Gestern haben Sie eine Anwendung und ein Applet unter Java geschrieben. Heute werden Sie die Sprache genauer kennenlernen, so daß Sie die grundlegenden Konstrukte kennen und wissen, wie sie für die Programmierung in Java eingesetzt werden.
Die folgenden Konstrukte stellen die elementaren Bausteine Ihrer Java-Programme dar, etwa Variablen, Typen, Ausdrücke, Operatoren, Arrays, Strings, Bedingungen und Schleifen. Heute lernen Sie die folgenden Dinge kennen:
In einer Programmanweisung erfolgt die eigentliche Arbeit. Es gibt drei Kategorien von Anweisungen: einfache Anweisungen, zusammengesetzte Anweisungen und Kommentare. Einfache und zusammengesetzt Anweisungen werden kompiliert und werden Teil der Binärdarstellung Ihrer Anwendung. Alle kompilierten Anweisungen müssen mit einem Semikolon enden (;). Kommentare werden nicht kompiliert und sind nur im Quellcode sichtbar. Sie können jedoch Monate später Bedeutung erlangen, wenn Sie (oder jemand, der Ihren Code warten soll) versuchen, herauszufinden, was der Code bewirken soll.
Einfache Anweisungen können Werte zuweisen, eine Aktion ausführen oder eine Methode aufrufen.
currpoint = new Point(x,y);
if (i==8) j = 4;
g.drawString("Hello world!", 10, 30);
repaint();
Java unterstützt verschiedene Anweisungstypen, unter anderem Zuweisung, Block, Bedingung, Deklaration, Schleifen und Methodenaufrufe. Bis auf die Methodenaufrufe werden all diese Typen heute beschrieben, die Methodenaufrufe folgen morgen.
Hier folgt ein Beispiel, das die unterschiedlichen Gültigkeitsbereiche demonstriert. In der folgenden Methodendefinition wird y zweimal deklariert: einmal für die Methode als Ganzes, einmal innerhalb des Blocks:
void testblock() {
int x = 10;
int y = 35;
[ // Blockanfang
int y = 50;
System.out.println("Im Block:");
System.out.println("x:" + x);
System.out.println("y:" + y); // einmal
] // block end
System.out.println("y:" + y); // zweimal
}
Wenn y zum ersten Mal ausgegeben wird, liegt der Gültigkeitsbereich innerhalb des Blocks, wo y redefiniert wurde, sein Wert ist also 50. Wenn y zum zweiten Mal ausgegeben wird, liegt der Gültigkeitsbereich innerhalb der Methode aber außerhalb des Blocks, der Wert von y ist also 35.
Blöcke werden in der Regel nicht so eingesetzt, wie in diesem Beispiel gezeigt. Häufig finden sie in Kontrollstrukturen Verwendung, die Sie später noch kennenlernen werden.
Kommentare sind Anweisungen, die nicht kompiliert werden, und die beliebige Texte enthalten können, die Sie dem Code zur Information mitgeben wollen. Java stellt drei Arten von Kommentaren zur Verfügung: einzeilige, mehrzeilige und Dokumentations-Kommentare.
Kommentare sind eine praktische interne Dokumentation Ihres Programms. Sie können Kommentare einfügen, um zu beschreiben, wie ein bestimmter Teil eines Programms entwickelt wurde, warum eine bestimmte Datenstruktur gewählt wurde oder welche Abhängigkeiten der Code aufweist. Wenn Sie eine externe Dokumentation besitzen, können Sie in Kommentaren darauf verweisen und so interne und externe Dokumentation verknüpfen.
Ein einzeiliger Kommentar kann in einer eigenen Zeile plaziert werden. Er wird durch zwei Schrägstriche (//) am Zeilenanfang gekennzeichnet werden:
// So sieht ein einzeiliger Kommentar aus
Ein einzeiliger Kommentar kann auch am Ende einer Codezeile plaziert werden, hinter dem Semikolon. Das wird manchmal auch als Inline-Kommentar bezeichnet:
x = y; // Dies ist ein Inline-Kommentar
Ein mehrzeiliger Kommentar wird durch eine Kombination aus Schrägstrich und Stern (/*) am Anfang
gekennzeichnet und endet mit einem Ste
/* Für diese Berechnung wird die Anzahl der Tage
pro Zyklus auf 30 gesetzt */
Hier eine alternative Darstellung mehrzeiliger Kommentare:
/* Die Prozedur AvgDailyBal berechnet den durchschnittlichen
* täglichen Saldo, das ist die Summe aller noch nicht bezahlten
* Rechnungen dividiert durch die Anzahl der Tage im aktuellen
* Monat des Rechnungszyklus
*/
Beachten Sie, daß die Sterne am Anfang der Mittelzeile in diesem Beispiel nur dem visuellen Effekt dienen. Sie können einen beliebigen Stil für Ihre mehrzeiligen Kommentare verwenden, solange sie mit /* beginnen und mit */ enden. Der Compiler ignoriert einfach alles, was zwischen diesen Zeichenpaaren steht.
Dokumentations-Kommentare sind speziell darauf ausgelegt, eine öffentliche Klassendokumentation im HTLM-Format zu erzeugen. Auch die HTML-Seiten, die Java dokumentieren, wurden unter Verwendung dieser Technik erzeugt, indem Dokumentations-Kommentare in den Quellcode aufgenommen wurden. Alles, was zwischen Schrägstrich-Stern-Stern (/**) und Stern-Schrägstrich (*/) steht, wird als Teil dieses speziellen Kommentars betrachtet. Diese Kommentare werden mit dem Utility javadoc aus dem Code extrahiert, die auch bestimmte Schlüsselwort-Variablen versteht, die mit dem At-Zeichen (@) beginnen, wie etwa @author oder @version:
/** Diese öffentliche Klasse soll die
* Funktionalität der Klasse awt.Graphics
* erweitern, um Firmenlogos anzuzeigen.
* @author mmm
* @version 3.51
*/
public class mySpecialClass extends java.awt.Graphics {
...
}
Dokumentations-Kommentare werden in der Regel unmittelbar vor der Klassendeklaration plaziert, wie in diesem Beispiel gezeigt.
Variablen sind Platzhalter mit Namen, in denen während der Programmausführung Werte gespeichert werden. Bevor Sie eine Variable verwenden können, müssen Sie festlegen, welche Art Wert sie aufnehmen soll. Das hängt davon ab, welchen Datentyp Sie für die Deklaration der Variablen verwenden. Nachdem eine Variable deklariert wurde, kann sie durch Zuweisung eines Werts initialisiert und in Ausdrücken verwendet werden. Bei dem zugewiesenen Wert kann es sich um einen literalen Wert (eine Zahl, einen Buchstaben oder einen String) oder um das Ergebnis eines Ausdrucks handeln.
Java kennt drei Kategorien von Variablen: Klassenvariablen, Instanzvariablen und lokale Variablen. Alle drei werden auf dieselbe Weise deklariert. Allerdings erfolgt der Zugriff auf Klassen- und Instanzvariablen etwas anders als auf lokale Variablen. Klassen- und Instanzvariablen werden in der nächsten Lektion beschrieben. Heute lernen Sie die lokalen Variablen kennen, die in Methoden deklariert und verwendet werden.
Wenn Sie eine Variable mit einem bestimmten Datentyp deklarieren, definieren Sie, welche Art von Werten dort abgelegt werden können. Eine Variable beispielsweise, die mit dem Typ byte deklariert wurde, kann einen Wert zwischen -128 und 127 aufnehmen. Es gibt acht Wert-Datentypen und zwei Referenz-Datentypen.
Die acht Werttypen beinhalten Integer-, Fließkomma-, Boolesche und Zeichen-Datentypen, wie in Tabelle 2.1 gezeigt. Sie werden manchmal auch als elementare Typen bezeichnet. Alle Datentypen in Java haben Standardwerte sowie konsistente Eigenschaften über alle Plattformen hinweg.
Tabelle 2.1: Die Wert-Datentypen von Java
Alle Integer-Typen (byte, short, int, long) sind vorzeichenbehaftet. Fließkomma-Werte (float und double) folgen dem IEEE-Standard 754 für Zahlen mit einfacher und doppelter Genauigkeit. Neben den normalen numerischen Werten können Fließkomma-Operationen vier spezielle Werte zurückgeben, die als Konstanten definiert sind: POSITIVE_INFINITY, NEGATIVE_INFINITY, NEGATIVE_ZERO und NaN (Not a Number, keine Zahl). Die boolean-Werte werden nicht zu Integern ausgewertet wie in anderen Sprachen; sie können nur die Werte true oder false darstellen.
Wenn die char-Zeichenwerte etwas seltsam aussehen, dann, weil Java den Zwei-Byte-Unicode-Zeichenstandard unterstützt. In diesem Buch werden Sie jedoch nur die ASCII- oder Latin-1-Untermenge der Unicode-Zeichen verwenden.
Die beiden Referenz-Typen nehmen Objekte (in der nächsten Lektion beschrieben) und Arrays auf. Der Referenz-Typ object kann eine Klasseninstanz aufnehmen und hat den Standardwert null. Der Referenz-Typ array kann Elemente beliebiger Werte oder Referenz-Typen aufnehmen und hat als Standardwert den Standardwert für den Elementtyp. Mit anderen Worten, die Elemente eines byte-Arrays haben die Standardwerte 0, die Elemente eines object-Arrays haben die Standardwerte null.
Ein Variablenname kann mit einem Buchstaben, einem Unterstrich (_) oder einem Dollarzeichen ($) beginnen und eine beliebige Kombination aus Buchstaben und Ziffern enthalten. Symbole sind problematisch, weil viele davon in Java-Operatoren verwendet werden. Außerdem ist es empfehlenswert, nicht den Unterstrich (_) oder das Dollarzeichen ($) als erstes Zeichen zu verwenden, auch wenn das erlaubt ist, um Konflikte beim Linken von C/C++-Bibliothekn zu vermeiden.
Der De-facto-Standard ist, in Variablennamen nur Buchstaben und Ziffern zu verwenden, wobei das erste Wort in Kleinbuchstaben dargestellt wird, das zweite und alle folgenden mit einem großen Anfangsbuchstaben. Eine Variable beispielsweise, die das Rentenalter angibt, könnte als rentenAlter bezeichnet werden, eine Variable für einen Versicherungshöchstwert als versWertLimit.
Java-Schlüsselwörter können nicht als Variablennamen verwendet werden. Es gibt zwei Schlüsselwörter, die Tabelle 2.2 aufgelistet sind, die zwar nicht mehr verwendet aber dennoch von Java reserviert werden. Sie sind durch das Kreuz (å) markiert.
Tabelle 2.2: Java-Schlüsselwörter
Nachdem Sie wissen, welchen Typ und welchen Namen eine Variable haben soll, können Sie sie deklarieren. Java bietet mehrere Möglichkeiten zur Variablendeklaration, die an einer beliebigen Stelle in der Methode plaziert werden kann. Eine Variable ist von dem Moment an, wo sie deklariert wurde, bis zur nächsten schließenden geschweiften Klammer (}) gültig. Hier hört die Variable auf zu existieren und verschwindet aus dem Gültigkeitsbereich.
short retireAge;
short birthYear;
float insValueLimit;
Wenn Sie Variablen desselben Typs deklarieren, können Sie sie in einer einzigen Zeile angeben, abgetrennt durch Kommata. Die drei Variablen der folgenden Deklaration haben alle den Typ short:
short retireAge, birthYear, stdRetireAge;
Um einer Variablen bei der Deklaration einen Ausgangswert zuzuweisen, verwenden Sie das Gleichheitszeichen (=), den Zuweisungsoperator von Java, gefolgt von dem gewünschten Wert. Die folgende Deklarationsanweisung initialisiert insValueLimit mit 10000.00:
float insValueLimit = 10000.00;
Sie können mehrere Variablen initialisieren, indem Sie jeder davon einen Wert zuweisen und sie in einzelnen Zeilen deklarieren. In der folgenden Deklaration werden isInsured und isCurrent mit true initialisiert:
boolean isInsured = true;
boolean isCurrent = true;
Alternativ können Sie mehrere Variablen desselben Typs initialisieren, indem Sie sie in dieselbe Zeile schreiben und durch Kommata voneinander trennen. Jede dieser Möglichkeiten ist korrekt und nur eine Frage des Geschmacks und des Stils.
boolean isInsured = true, isCurrent = true;
Im nächsten Beispiel werden retireAge und birthYear nicht initialisiert, aber der letzten Variablen, stdRetireAge, wird der Wert 65 zugewiesen.
short retireAge, birthYear, stdRetireAge = 65;
Variablen können zwar den Standardwert des Typs annehmen, mit dem sie deklariert werden, aber der Java-Compiler gibt eine Warnung aus, wenn Sie versuchen, eine lokale Variable zu verwenden, ohne sie zuvor initialisiert zu haben.
Literale werden in Java verwendet, um Werte darzustellen. Im letzten Abschnitt, der die Zuweisung von Werten an Variablen beschrieb, sind alle zugewiesenen Werte (10000.00, true und 65) Literale.
Die Booleschen Literale in Java können keine Integer-Werte annehmen. Sie werden nur zu true und false ausgewertet und haben den Typ boolean.
Ein numerisches Literal ist eine beliebige Zahl, die einem Integer- oder Fließkomma-Typ zugewiesen werden kann. Der Typ eines Literals wird durch spezielle Typbezeichner spezifiziert, die in Klein- oder Großbuchstaben dargestellt werden können.
Standardmäßig hat ein Integer-Literal wie etwa 8 den Wert int. Wenn ein Integer-Literal jedoch zu lang ist, um in einen int zu passen, etwa 3000000000 (3 Milliarden), wird automatisch ein long daraus gemacht. Sie können auch explizit angeben, daß ein Literal ein long sein soll, indem Sie am Ende der Zahl den Typbezeichner l oder L einfügen: 8L ist ein long-Literal. Alle Integer-Typen können als negative Zahlen verwendet werden, indem ihnen ein Minuszeichen vorangestellt wird: -78 ist ein negatives int-Literal.
Integer können auch als oktal oder hexadezimal dargestellt werden. Durch Voranstellen einer 0 wird eine Zahl zu einem oktalen Literal (Basis 8): 0347. Voranstellen von 0x oder 0X gibt an, daß es sich bei der Zahl um ein hexadezimales Literal (Basis 16) handelt: 0x2FA3. Sie können zwar das oktale oder hexadezimale Zahlensystem verwenden, um ein Literal darzustellen, aber im Speicher wird es als einer der Integer-Datentypen abgelegt, entsprechend der Regeln aus dem vorherigen Absatz.
Fließkomma-Literale haben immer den Typ double, egal, welchen Wert sie haben: 5.67 ist ein double-Literal. Sie können ein Literal zu einem float machen, indem Sie am Ende der Zahl ein f oder F einfügen: 5.67f wird zu einem float-Literal. Sie können auch die wissenschaftliche Notation verwenden, um Fließkomma-Literale darzustellen, indem Sie ein e oder E anfügen, gefolgt von dem Exponenten: 5.964e-4 ist die Darstellung von 0.0005964 in wissenschaftlicher Notation, die als double abgelegt wird. Um sie als float abzulegen, fügen Sie f oder F am Ende des Literals an: 5.964e-4F.
Zeichen-Literale in Java, dargestellt durch Unicode-Zeichen des Datentyps char, werden in einfache Anführungszeichen eingeschlossen ('), etwa 'a', '*' oder '8'. Nicht-druckbare Zeichen wie etwa der Tabulator oder Backspace können ebenfalls als Zeichen-Literale dargestellt werden, indem sogenannte Escape-Codes verwendet werden.
In dieser Darstellung steht das Zeichen d in oktalen Codes für eine Ziffer (0-7), in hexadezimalen und Unicode-Zeichencodes für eine hexadezimale Ziffer (0-9, a-f, A-F).
Ein String ist einfach nur eine Kombination aus Zeichen-Literalen. Er wird in doppelte Anführungszeichen (») eingeschlossen, z.B. "Hello world!". Um einen Null-String darzustellen - d.h. einen String, der keine Zeichen enthält -, geben Sie einfach das doppelte Anführungszeichen zweimal hintereinander an, also »«.
Es folgt ein Code-Ausschnitt, dessen Anweisungen je ein String-Literal enthalten:
System.out.println("Dieser String gibt\nmehrere Zeilen aus.");
System.out.println("\"Ich will allein sein\", sagte sie.");
System.out.println("Die meisten 4.-Klässler sind \11 Jahre alt.");
In der ersten Zeile wird das \n-Literal verwendet, daß nach dem Wort gibt ein Neuezeile-Zeichen ausgegeben werden soll. Im zweiten Beispiel wird mit Hilfe des Literals \" ein doppeltes Anführungszeichen ausgegeben. Die dritte Zeile verendet ein oktales Literal, \11, um die Zahl 9 in der Ausgabe zu erzeugen. Und so sieht die Ausgabe aus:
Ausdrücke und Operatoren ermöglichen Ihnen, Auswertungen von Daten vorzunehmen.
Jeder Operator hat eine bestimmte Priorität, die angibt, in welcher Reihenfolge er und seine Operanden in einem Ausdruck ausgewertet werden. Es gibt bei der Arbeit mit Operatoren noch mehrere andere Aspekte zu berücksichtigen. Es gibt drei Arten von Operatoren: unäre Operatoren, die einen Operanden entgegennehmen, binäre Operatoren, die zwei Operanden entgegennehmen, und ternäre Operatoren, die drei Operatoren entgegennehmen. Unäre Operatoren können auch ein Präfix oder Postfix darstellen und so ganz andere Ergebnisse bewirken. Binäre und ternäre Operatoren sind Infix-Operatoren.
Java kennt zahlreiche Zuweisungsoperatoren, die Sie später in diesem Kapitel noch kennenlernen werden. Hier müssen Sie nur wissen, daß der Zuweisungsoperator (=) den Wert des Ausdrucks auf der rechten Seite der Variablen auf der linken Seite zuweist.
Technischer Hinweis
In Java führt der Zuweisungsoperator = dieselbe Operation aus wie in Pascal/Delphi der
Zuweisungsoperator :=.
Operationen, die numerische Operanden entgegennehmen und numerische Ergebnisse erzeugen, werden als arithmetische Operationen bezeichnet. Die unären Operatoren beinhalten Inkrement, Dekrement, Plus und Minus. Die binären Operatoren umfassen Addition, Subtraktion, Multiplikation, Division und Modulo. Es gibt sogar eine arithmetische binäre Operation für Strings, die sogenannte Konkatenation.
Die Inkrement- (++) und Dekrement-Operatoren (--) sind sowohl unäre Präfix- als auch unäre Postfix-Operatoren. Wenn der Operator vor dem Operanden steht (Präfix), erfolgt die Operation, bevor Ihr Programm den resultierenden Wert weiterverwendet. Wenn der Operator hinter dem Operanden steht (Postfix), verwendet Ihr Programm den Wert und führt dann erst die Operation aus. Bei der Inkrementierung eines Werts wird 1 addiert, bei der Dekrementierung wird 1 subtrahiert. Mit anderen Worten, die Ausdrücken in den beiden:
myAge = myAge + 1;
daysLeftUntilVacation = daysLeftUntilVacation - 1;
stellen dieselben Werte dar wie die beiden folgenden Ausdrücke:
myAge++;
daysLeftUntilVacation--;
Der Wert kann ein Integer- oder Fließkomma-Wert sein oder ein beliebiger Ausdruck, der einen dieser numerischen Typen ergibt.
Betrachten Sie beispielsweise den folgenden Programmausschnitt:
int x, y, z = 7;
x = ++z; // In diesem Beispiel wird x der Wert 8 zugewiesen
y = z; // Der Variablen y wird ebenfalls der Wert 8 zugewiesen
Die Variable z wird zuerst inkrementiert, anschließend wird das Ergebnis, 8, der Variablen x zugewiesen. Weil z jetzt 8 ist, wird auch y dieser neue Wert zugewiesen. Betrachten Sie jetzt den folgenden Programmausschnitt:
int x, y, z = 7;
x = z++; // Hier wird x der Wert 7 zugewiesen
y = z; // Der Variablen y wird der Wert 8 zugewiesen
Der Wert der Variablen z, wird x zugewiesen. Anschließend wird z inkrementiert und enthält jetzt den Wert 8. Weil z gleich 8 ist, erhält y ebenfalls den Wert 8.
Um einem numerischen Wert oder einem Ausdruck ein Vorzeichen zu geben, stellen Sie ihm einfach das unäre Plus (+) für positive Zahlen oder das unäre Minus (-) für negative Zahlen voraus. Hier ein Beispiel:
int x, y = +4, z = -7;
x = y; // x wird der Wert 4 zugewiesen
x = -y; // x wird der Wert -4 zugewiesen
x = -z; // x wird der Wert 7 zugewiesen
x = z; // x wird der Wert -7 zugewiesen
Diese Gruppe der binären Infix-Operatoren führt die grundlegenden mathematischen Operationen aus, wie etwa Addition (+), Subtraktion (-), Multiplikation (*), Division (/) und Modulo (%). Die Operationen werden im folgenden gezeigt:
int x, y = 28, z = 8;
x = y + z; // x wird der Wert 36 zugewiesen.
x = y - z; // x wird der Wert 20 zugewiesen.
x = y * z; // x wird der Wert 224 zugewiesen.
x = y / z; // x wird der Wert 3 zugewiesen.
x = y % z; // x wird der Wert 346 zugewiesen.
Der Divisions-Operator (/) verwirft den Rest der Division und gibt nur den Integer-Bestandteil als Ergebnis zurück. Der Modulo-Operator (%) gibt nur den Rest der Division als Ergebnis zurück.
Wie kann man Strings addieren? Durch Konkatenation (+) mit dem binären Infix-Operator:
string helloWorld, hello = "Hello", space = " ", world = "World";
helloWorld = hello + space + world + "!"
string helloWorld = "Hello World!"
Dem Konkatenations-Operator (+) können Sie beliebige Strings oder Zeichen-Literale als Operanden übergeben.
Alle relationalen Operatoren geben ein Boolesches Ergebnis zurück. Der Ausdruck, den der Operator und seine Operanden bilden, ist entweder true oder false. Diese Operatoren werden auch als Vergleichsoperatoren bezeichnet. Neben arithmetischen Werten können Sie auch Objekte und Typen vergleichen.
Die arithmetischen relationalen Operatoren sind Kleiner (<), Kleiner-Gleich (<=), Größer (>), Größer-Gleich (>=), Gleich (==) und Ungleich (!=). Hier einige Beispiele für ihre Arbeitsweise:
int w = -8, x = -9, y = 28, z = 8;
boolean isThatSo;
isThatSo = y < z; // isThatSo erhält den Wert false.
isThatSo = x <= w; // isThatSo erhält den Wert true.
isThatSo = y > z; // isThatSo erhält den Wert true.
isThatSo = x >= w; // isThatSo erhält den Wert false.
isThatSo = y == z; // isThatSo erhält den Wert false.
isThatSo = w != z; // isThatSo erhält den Wert true.
Es gibt auch relationale Operatoren für den Vergleich von Objekten: Typvergleich (instanceof), Bezieht-sich-auf-dasselbe-Objekt (==) und Bezieht-sich-auf-ein-anderes-Objekt (!=). Diese beiden letzten Operatoren sehen aus wie die oben gezeigten Operatoren für den Vergleich auf Gleichheit (==) und Ungleichheit (!=), aber sie nehmen Objekte als Operanden entgegen.
Der Typvergleichs-Operator (instanceof) stellt fest, ob das Objekt im linken Operanden eine Instanz des Typs des rechten Operanden ist (oder die Schnittstelle implementiert). Ist dies der Fall, ist das Ergebnis gleich true, andernfalls oder wenn das Objekt null ist, ist das Ergebnis false.
Der Bezieht-sich-auf-dasselbe-Objekt-Operator (==) stellt fest, ob das Objekt im linken Operanden auf dieselbe Instanz verweist wie das Objekt im rechten Operanden. Wenn das der Fall ist, ist das Ergebnis gleich true, andernfalls ist es false.
Der Bezieht-sich-auf-ein-anderes-Objekt-Operator (!=) stellt fest, ob das Objekt im linken Operanden auf eine andere Instanz als das Objekt im rechten Operanden verweist. Wenn das der Fall ist, ist das Ergebnis gleich true, andernfalls ist es false.
Logische Operatoren nehmen Boolesche Operanden entgegen und geben ein Boolesches Ergebnis zurück. Es gibt einen unären logischen Präfix-Operatoren: Logisch NICHT (!). Fast alle anderen sind binäre Infix-Operatoren: logisch UND (&), logisch ODER (|), logisch XODER (^), bedingtes UND (&&) und bedingtes ODER (||). Schließlich gibt es noch einen ternären Infix-Operator: if-else (?:).
Das logische NICHT (!) wechselt einfach den Booleschen Wert, dem es vorangestellt wird. Ist der Operand true, ist das Ergebnis false, ist der Operand false, ist das Ergebnis true, also einfach immer das Gegenteil.
Der logische UND-Operator (&) wertet beide Operanden aus. Sind beide true, ist das Ergebnis true. Ist einer der Operanden false, ist das Ergebnis false. Der logische ODER-Operator (|) stellt fest, ob einer der beiden Operanden gleich true ist, dann ist das Ergebnis true, wenn beide false sind, ist das Ergebnis false. Der logische XODER-Operator (^) stellt fest, ob die Operanden unterschiedlich sind; einer muß true sein, der andere muß false sein, und wenn das so ist, ist das Ergebnis true; sind beide true oder beide false, ist das Ergebnis false. Tabelle 2.4 zeigt diese Ergebnisse.
Tabelle 2.4: Logische Boolesche Operationen und Ergebnisse
Der bedingte UND-Operator (&&) und der bedingte ODER-Operator (||) sind gleich dem logischen UND (&) und ODER (|), aber die Auswertung erfolgt etwas anders.
Der bedingte UND-Operator (&&) stellt zuerst fest, ob der linke Operand false ist. In diesem Fall ist das Ergebnis false und die Auswertung wird beendet. Wenn der linke Operand true ist, wird auch der rechte Operand ausgewertet. Ist der rechte Operand ebenfalls true, ist das Ergebnis true, ist er false, ist das Ergebnis false.
Der bedingte ODER-Operator (||) stellt zuerst fest, ob der linke Operand true ist. In diesem Fall ist das Ergebnis true und die Auswertung wird beendet. Wenn der linke Operand false ist, wird auch der rechte Operand ausgewertet. Wenn der rechte Operand true ist, ist das Ergebnis true, ist er false, ist das Ergebnis false.
Der ternäre bedingte Infix-Operator (?:) ist eine Abkürzung für die if-else-Anweisung. Wenn der erste Operand gleich true ist, wird der zweite Operand ausgewertet/ausgeführt, andernfalls der dritte Operand:
isInsured ? payClaim() : doNothing();
Wenn in diesem Beispiel isInsured gleich true ist, wird die Methode payClaim() aufgerufen. Ist isInsured false, wird die Methode doNothing() aufgerufen. Mehr über Methodenaufrufe erfahren Sie in der nächsten Lektion.
Bitweise Operationen manipulieren die Bits im Speicherraum der Variablen. Bitweise Operatoren nehmen Integer-Operanden entgegen und geben ein Integer-Ergebnis zurück. Es gibt einen unären bitweisen Präfix-Operator: bitweise NICHT (ÿ). Alle anderen sind binäre Infix-Operatoren: bitweises UND (&), bitweises ODER (|), bitweises XODER (^), bitweiser Linksshift (<<), vorzeichenbehafteter Rechtsshift (>>) und Rechtsshift mit Auffüllen durch Nullen (>>>).
Der bitweise NICHT-Operator (ÿ) wechselt einfach die Bits des Integer-Werts, dem er vorausgestellt wird. Wenn der Integer-Wert beispielsweise 00101001 ist, würde die Anwendung des NICHT-Operators (ÿ) den Integer-Wert 11010110 ergeben.
In einer Programmanweisung wären die beiden Integer-Werte links und rechts vom bitweisen Operator angesiedelt, des besseren Vergleichs halber ist es aber besser, sie in einem Spaltenformat zu zeigen. Für das bitweise UND (&), das bitweise ODER (|) und das bitweise XODER (^) vergleichen Sie die Bits gleicher Positionen im Integer-Wert. Betrachten Sie beispielsweise die beiden folgenden Zahlen:
Das erste Paar ist (0,1), das zweite Paar ist (0,1) und das dritte Paar ist (1,1). Sie müssen die Paare vertikal in den Spalten bilden, so daß Sie jeweils dieselben Positionen für den Vergleich heranziehen. In diesem Beispiel stellt die Zahl 00101001 den linken Operanden, die Zahl 11101110 den rechten Operanden dar. Tabelle 2.5 zeigt die Ergebnisse der möglichen Bit-Paarungen in bitweisen Operationen.
Tabelle 2.5: Bitweise Operationen und ihre Ergebnisse
Die bitweisen Shift-Operatoren sind bitweiser Linksshift (<<), vorzeichenbehafteter Rechtsshift (>>) und Rechtsshift mit Auffüllen durch Nullen (>>>). Diese Operatoren nehmen als linken Wert einen Integer-Wert entgegen, als rechten Operanden ein numerisches Literal, das angibt, um wie viele Stellen geshiftet werden soll.
Der bitweise Linksshift (>>) verschiebt jede Ziffer im Integer-Wert in dem linken Operanden um so viele Stellen nach links, wie das numerische Literal im rechten Operand angibt. Beim Verschieben »fallen« die Ziffern links heraus und rechts werden Nullen eingefügt, um die neue Zahl zu bilden. Weil das Vorzeichen herausfällt, wenn Sie nach links verschieben, sollten Sie bei der Verwendung dieses Operators vorsichtig sein.
Der vorzeichenbehaftete Rechtsshift (>>) verschiebt jede Ziffer im Integer-Wert im linken Operanden um so viele Stellen nach rechts, wie im rechten Operanden angegeben. In diesem Fall geht das Vorzeichen jedoch nicht verloren. Der Inhalt der ganz linken Position bleibt unverändert und die Zahl wird links mit dem Wert der linken Ziffer aufgefüllt.
Wenn Sie links explizit mit Nullen auffüllen wollen, verwenden Sie den Rechtsshift-Operator mit Auffüllen durch Nullen (>>>). Weil diese Operation immer Nullen einfüllt, behält sie das Vorzeichen nicht bei.
Java bietet zahlreiche Zuweisungsoperatoren. Der grundlegende Zuweisungsoperator (=) wird durch mehrere Operatoren übernommen, die Ihnen eine gleichzeitige Operation und Zuweisung ermöglichen. Sie haben die Form op=, wobei op ein Operator aus der folgenden Menge sein kann:
{ *, /, %, +, -, <<, >>, >>>, &, ^, | }
Statt also beispielsweise die folgenden Anweisungen zu verwenden:
totalCharges = totalCharges + newItemPrice;
isInsured = isInsured & hasRenewed;
könnten Sie auch folgendes schreiben:
totalCharges += newItemPrice;
isInsured &= hasRenewed;
Sie nehmen dieselben Operationen vor. Diese Zuweisungsoperatoren können viel Zeit sparen, wenn Sie sehr lange Namen verwenden, und außerdem wird Ihr Code besser lesbar.
Sie haben jetzt alle Operatoren von Java kennengelernt, aber es bleibt noch ein Aspekt zu berücksichtigen. Die Priorität der Operatoren legt fest, in welcher Reihenfolge die Argumente eines Ausdrucks ausgewertet werden. Tabelle 2.6 zeigt die einzelnen Operatoren, aufgelistet ihrer Priorität nach, zusammen mit dem Operandentyp, den sie entgegennehmen, der ausgeführten Operation sowie der Plazierung des Operators im Ausdruck. Betrachten Sie beispielsweise den folgenden Programmausschnitt:
int g, x = 6, y = 7, z = 8;
g = x + y * z;
Hier ist die Multiplikation von y und z zuerst ausgeführt und ergibt 56. Das Ergebnis wird zu x addiert, was 62 ergibt. Dieses Ergebnis schließlich wird g zugewiesen. Aber was sollten Sie tun, wenn Sie x und y zuerst addieren und dann mit z multiplizieren wollen? Mit Hilfe von Klammern können Sie die Auswertungsreihenfolge beeinflussen:
int g, x = 6, y = 7, z = 8;
g = (x + y) * z;
In diesem Fall werden zuerst x und y addiert, ergeben 13, und dieser Wert wird mit z multipliziert, was 104 ergibt. Dieser Wert wird schließlich g zugewiesen. Operatoren derselben Prioritätsstufe werden von links nach rechts ausgewertet, außer verschachtelte unäre und ternäre Operatoren, die von rechts nach links ausgewertet
Tabelle 2.6: Operatorpriorität
Neben diesen Operationen gibt es noch zahlreiche mathematische Konstanten und Operationen, die in der Klasse java.math.lang definiert sind. Diese Funktionen können in diesem Buch nicht erklärt werden. Es gibt Konstantenwerte für e und Pi, trigonometrische Funktionen (Sinus, Cosinus, Tangens, Arkussinus, Arkuscosinus, Arkustangens) sowie Funktionen für exponentielles E, den natürlichen Logarithmus, Quadratwurzel, IEEE-Rest, Oberwert, Unterwert, Umwandlung in Polarkoordinaten, Exponenten, Rundung, Zufallszahlen, Absolutwert, Maximum und Minimum.
Arrays sind eines der praktischsten Konstrukte in Java. Sie ermöglichen Ihnen, Objekte oder elementare Typen in einfach zu verwaltenden Strukturen zusammenzufassen. Strings sind ein Sonderfall von Arrays und voll-funktionale Objekte in Java. In diesem Abschnitt lernen Sie, diese Objekte zu erzeugen und zu manipulieren.
In Java werden Arrays als voll-funktionale Objekte implementiert, sie können also auch wie Objekte verglichen oder manipuliert werden. Weil Arrays echte Objekte sind, haben sie Konstruktoren, Methoden und Variablen, die speziell für die Verwendung in Arrays geschaffen wurden.
Um in Java ein Array zu erzeugen, gehen Sie in drei Schritten vor:
Der erste Schritt beim Erzeugen eines Arrays ist, eine Variable zu erzeugen, die das Array aufnimmt. Die Deklaration von Array-Variablen kann eines von zwei gleichermaßen möglichen Formaten annehmen. Beide Formate geben den Namen des Array an, den Typ der Objekte, die das Array aufnimmt, sowie ein leeres Klammernpaar ([]), das anzeigt, daß es sich bei der neuen Variablen um ein Array handelt. Hier einige typische Deklarationen von Array-Variablen:
int[] theTenBestGameScores;
Date games[];
int averageRBI[];
Die erste Deklaration gibt an, daß Sie ein Array des Typs int mit dem Namen theTenBestGameScores deklarieren. Die zweite deklariert ein Array vom Typ Date namens games. Die dritte deklariert ein int-Array namens averageRBI.
Wie Sie sehen, können die Klammern ([]) vor dem Typ oder nach dem Variablennamen angegeben werden. Der Java-Compiler akzeptiert beide Formen und die beiden Deklarationsstile können einfach kombiniert werden. Wenn die Klammern unmittelbar hinter dem Typ angegeben werden, sehen Sie sofort, daß Sie ein Array dieses Typs deklarieren. Der Java-Quellcode verwendet das in den beiden letzten Beispielen gezeigte Format, das Buch folgt deshalb diesem Standard und plaziert die Klammern unmittelbar hinter dem Variablennamen.
Im zweiten Schritt erzeugen Sie ein Array-Objekt und weisen es dieser Variablen zu. Es gibt zwei Möglichkeiten, das zu realisieren:
Wenn Sie ein Array-Objekt mit new erzeugen, müssen Sie explizit angeben, wie viele Elemente das Array aufnehmen soll:
Damit wird ein neues Array mit Integern mit 10 Elementen erzeugt. In diesem Fall wird jedes der 10 Elemente im Integer-Array mit dem Wert 0 initialisiert. Der initialisierte Wert hängt von dem Arraytyp ab, den Sie angelegt haben, wie in Tabelle 2.7 gezeigt.
Tabelle 2.7: Standardwerte für die Array-Initialisierung
Sie können ein Array auch gleichzeitig erzeugen und initialisieren, wie das auch für andere Variablen möglich ist. Statt new zu verwenden, um das Array-Objekt zu erzeugen, schließen Sie die Elemente des Arrays in geschweifte Klammern ein, getrennt durch Kommata, die die Ausgangswerte darstellen:
float rates[] = { 12.9, 14.5, 16.5, 18.95, 23.0 };
Die Elemente innerhalb der geschweiften Klammern müssen denselben Typ haben wie die Variable, die das Array aufnimmt. Das Array wird mit einer Anzahl von Einträgen erzeugt, die mit der Anzahl der hier übergebenen Elemente übereinstimmt. In diesem Beispiel wird also ein Array mit fünf float-Elementen angelegt und es erhält den Namen rates. Das erste Elemente im Array hat den Wert 12.9, das zweite hat den Wert 14.5 usw.
Ein Versuch, den falschen Datentyp in einem Array abzulegen, verursacht einen Compiler-Fehler. Die folgende Codezeile beispielsweise würde bewirken, daß sich der Compiler über einen Typfehler beschwert:
float rates[] = { 'M', 'i', 'a', 'm', 'i' };
Ein Versuch, zur Laufzeit einen falschen Typ im Array abzulegen, verursacht eine ArrayStoreException.
Um das Array für die oben gezeigten Werte korrekt zu deklarieren, müßten Sie ein Array vom Typ char anlegen:
char chArr[] = { 'M', 'i', 'a', 'm', 'i' };
Nachdem Sie ein Array initialisiert haben, können Sie die Werte in den einzelnen Array-Einträgen auswerten und ändern. Um auf einen Wert zuzugreifen, der in einem Array gespeichert ist, verwenden Sie den Index-Ausdruck für das Array.
arrayName ist die Variable, die das Array-Objekt aufnimmt. Der Index ist ein Integer oder ein Integer-Ausdruck, der den Eintrag des Arrays bestimmt, auf den zugegriffen werden soll.
Betrachten wir das Beispiel noch einmal:
float rates[] = { 12.9, 14.5, 16.5, 18.95, 23.0 };
Hier folgt ein Code-Ausschnitt, der zeigt, wie diese Werte anderen Variablen zugewiesen werden könnten:
float platinumRate == rates[0]; // Der Wert ist 12.9
float goldRate == rates[1]; // Der Wert ist 14.5
float preferredRate == rates[2]; // Der Wert ist 16.5
float regularRate == rates[3]; // Der Wert ist 18.95
float probationRate == rates[4]; // Der Wert ist 23.0
Alle Array-Indizes werden überprüft, um sicherzustellen, daß sie innerhalb der Array-Grenzen liegen. Sie müssen also größer oder gleich Null sein, aber kleiner als die Array-Länge. Diese Prüfung findet beim Kompilieren Ihres Java-Programms oder bei seiner Ausführung statt. In Java ist es nicht möglich, auf einen Wert in einem Array-Eintrag zuzugreifen, der außerhalb der Array-Grenzen liegt. Betrachten Sie die beiden folgenden Anweisungen:
int myArr[] = new int[10];
myArr[10] = 73;
Ein Programm mit dieser Anweisung erzeugt in der zweiten Zeile einen Compiler-Fehler, wenn Sie versuchen, es zu kompilieren. Das Array in myArr hat nur 10 Einträge, numeriert von 0 bis 9. Das Element am Index 10 würde in Eintrag Nummer 11 vorliegen, der nicht existiert, deshalb beschwert sich der Java-Compiler.
Wenn der Array-Index zur Laufzeit berechnet wird (beispielsweise innerhalb einer Schleife) und außerhalb der Array-Grenzen liegt, erzeugt der Java-Interpreter eine ArrayIndexOutOfBoundsException. Wenn Sie zur Laufzeit versuchen, ein Array mit weniger als Null Elementen zu allozieren (indem Sie etwa einen Index-Ausdruck verwenden, der eine negative Zahl ergibt), erhalten Sie eine NegativeArraySizeException.
Um zu vermeiden, daß in Ihren eigenen Programmen versehentlich über das Array-Ende hinaus zugegriffen wird, ermitteln Sie die Anzahl der Array-Elemente, indem Sie seine Instanzvariable length auswerten. Diese Variable ist für alle Array-Objekte definiert, unabhängig von deren Typ:
int len = arr.length; // ergibt 10
Um den Wert eines Array-Elements zu verändern, geben Sie einfach hinter dem Array-Zugriffsausdruck eine Zuweisung an. Hier zwei Beispiele:
myArr[1] = 15;
sentence[0] = "The";
Arrays elementarer Typen wie etwa int oder float können Werte von einem Eintrag in einen anderen kopieren. Ein Array mit Objekten ist jedoch in Java ein Array mit Verweisen auf diese Objekte (irgendwie ähnlich den Zeigern). Wenn Sie einem Array-Eintrag einen Wert hinzufügen, erzeugen Sie damit einen Verweis auf dieses Objekt, so wie für eine einfache Objekt-Variable. Wenn Sie ein Array-Objekt einem anderen zuweisen, wie etwa in der folgenden Codezeile:
dann weisen Sie einfach den Verweis noch einmal zu. Sie kopieren nicht den Wert von einem Eintrag in einen anderen. Nachdem diese Codezeile ausgeführt ist, zeigen sentence[10] und sentence[0] auf dieselbe Speicherstelle.
Arrays mit Verweisen auf Objekte sind im Gegensatz zu den Arrays mit den Objekten selbst besonders praktisch, weil sie Ihnen ermöglichen, mehrere Verweise auf dieselben Objekte sowohl innerhalb als auch außerhalb der Arrays zu verwenden. Sie können beispielsweise ein Objekt, das in einem Array enthalten ist, einer Variablen zuweisen und auf dieses Objekt über die Variable oder über seine Array-Position zugreifen.
In java.lang.System gibt es die Methode arraycopy(), die Ihnen ermöglicht, Daten von einem Array in ein anderes zu kopieren. Die Syntax für diese Methode lautet:
arraycopy(srcArr, srcOffset, dstArr, dstOffset, copyLength)
Die Argumente sind wie folgt definiert:
Diese Methode alloziert keinen Speicher, das Ziel-Array muß also bereits existieren.
Java unterstützt mehrdimensionale Arrays. Man kann sich ein mehrdimensionales Arrays ganz einfach als Array mit Arrays vorstellen. In Java deklarieren Sie ein Array mit Arrays (und diese Arrays können wiederum Arrays enthalten usw.) und greifen auf ihre Elemente zu, indem Sie für jede Dimension einen Index spezifizieren. Hier folgt ein Beispiel für ein zweidimensionales Array mit Koordinaten:
int coords[][] = new int[12][12];
coords[0][0] = 1;
coords[0][1] = 2;
Mehrdimensionale Arrays können beliebig viele Dimensionen haben, für die Sie jeweils nur ein neues Klammernpaar ([]) einfügen müssen. Hier folgt ein Beispiel für eine sechsdimensionale Arraydeklaration:
int sixDimArr[][][][][][] = new int [2][4][8][3][][];
In diesem Beispiel wird den ersten vier Dimensionen explizit eine Größe zugewiesen, den beiden letzten dagegen nicht. Der Speicher wird für die ersten vier Dimensionen alloziert. Die beiden letzten Dimensionen werden erst später bei der Initialisierung alloziert. Sie können beliebig viele Dimensionen mit explizit definierter Größe angeben, gefolgt von beliebig vielen Dimensionen ohne Größenangabe, aber in eben dieser Reihenfolge. Das folgende ist in Java nicht erlaubt:
int threeDimArr[][][] = new int [3][][3]; // invalid
Außerdem müssen die einzelnen Teil-Arrays eines mehrdimensionalen Arrays nicht dieselbe Größe haben. Betrachten Sie das folgende Beispiel:
byte threeDimByteArr[][][] = new byte [2][4][3];
Dieses Array enthält acht Teil-Arrays, die jeweils drei byte-Elemente enthalten. Das Array threeDimByteArr enthält zwei Elemente, die Teil-Arrays sind. Diese beiden Teil-Arrays haben je vier Elemente, bei denen es sich ebenfalls um Teil-Arrays handelt. Diese vier Teil-Arrays haben je drei byte-Elemente. Sie können mehrdimensionale Arrays auch durch die Vorgabe literaler Werte deklarieren, etwa wie folgt:
String encryptStrArr[][] = new String {
{"ab", "cd", "ef", "gh", "ij"},
{"kl", "mn"}
{"op", "qr", "st", "uv"}
{"wx", "yz"}
};
Dieses Beispiel erzeugt ein mehrdimensionale String-Array, das vier Teil-Arrays mit 5, 2, 4 und 2 String-Elementen alloziert.
In diesem Abschnitt werden Sie erfahren, wie man Strings deklariert und erzeugt, wie man auf String-Elemente zugreift und wie die Methoden zur String-Manipulation eingesetzt werden. Darüber hinaus werden Sie die Klasse StringBuffer kennenlernen.
Die Klasse String repräsentiert konstante Strings. Die String-Klasse stellt einige grundlegende Methoden zur Manipulation bereit, aber das Ergebnis muß immer einem zweiten String-Objekt zugeordnet werden. Jede Änderung in einem String-Wert macht eine Zuweisung an einen StringBuffer erforderlich. Die Klasse StringBuffer ermöglicht Ihnen, Strings direkt zu manipulieren und das Ergebnis der StringBuffer-Variablen zuzuweisen. Dazu muß in der Regel mehr Speicher alloziert werden, deshalb sollten Sie überall wo es möglich ist, String-Objekte verwenden.
Die Deklaration einer String- oder StringBuffer-Variablen ist ganz einfach:
String myString;
StringBuffer myStringBuff;
Die Deklaration der Variablen setzt einfach nur den Namen. Für das StringBuffer- oder String-Objekt wird kein Speicher alloziert. Dazu müssen Sie es unter Verwendung eines String-Literals oder eines Konstruktoraufrufs initialisieren.
Weil Strings so häufig verwendet werden, definiert die Klasse String in Java mehrere Abkürzungen, damit Strings ohne den expliziten Aufruf eines Konstruktors aufgerufen werden können. Die gebräuchlichste Methode zur Allozierung eines String-Objekts ist, der deklarierten Variablen ein String-Literal zuzuweisen:
myString = "Wir sehn uns am Samstag.";
Es werden so viele Zeichen alloziert, wie im Literal enthalten sind. Eine andere Möglichkeit, einen neuen String zu erzeugen, ist die Verwendung einer der valueOf()-Methoden, um einen Wert eines anderen Typs, etwa einen Integer, in sein Textäquivalent zu konvertieren:
myString = String.valueOf(17);
Damit wird ein String erzeugt, der zwei Zeichen enthält, 1 und 7. Die Methode valueOf() kann elementare Typen (boolean, char, int, long, float, double), den Object-Typ sowie char-Arrays konvertieren. Für char-Arrays stellt die Methode valueOf() zwei Methodensignaturen bereit: eine, die das gesamte Array als Parameter entgegennimmt, und eine zweite, die ein char-Array, einen Offset sowie einen Zähler als Parameter entgegennimmt. Hier ein Beispiel für die zweite Methode:
char chArr[] = { 's', 'p', 'o', 'r', 't', 's' };
String chString = String.valueOf(chArr, 1, 4);
Dadurch wird ein String namens chString erzeugt, der die in chArr definierten Zeichen enthält, beginnend an Position 1 (dem Offset) und über 4 Zeichen gehend (dem Zähler). Das resultiert schließlich in dem Wert port.
Beachten Sie, daß diese Techniken zum Erzeugen von String-Objekten keine explizite Verwendung eines Konstruktors erforderlich machen. Darüber hinaus definiert die String-Klasse mehrere Konstruktoren, aus denen Sie auswählen können. Hier einige der gebräuchlicheren Beispiele:
Hier sehen Sie den Standard-Konstruktor, der keine Parameter entgegennimmt:
Er erzeugt ein String-Objekt mit dem Namen strl1 und der Länge length gleich 0.
Dieser Konstruktor nimmt ein String-Literal als Parameter entgegen:
String str2 = new String("Guten Tag");
Er erzeugt das String-Objekt str2 mit dem Wert Guten Tag und einer Länge length von 9.
Der nächste nimmt ein chArr als Parameter entgegen:
char chArr[] = { 'S', 'a', 'm', 's' };
String str3 = new String(chArr);
Er erzeugt das String-Objekt str3 mit dem Wert Sams und einer Länge length von 4. (Diese Methode ist im Ergebnis identisch mit der valueOf()-Methode, die ein char-Array als Parameter entgegennimmt.)
Der nächste nimmt ein char-Array, einen Offset und einen Zähler als Parameter entgegen:
char chArr[] = { 'I', 'n', 't', 'e', 'r', 'n', 'e', 't' };
String str4 = new String(chArr, 5, 3);
Er erzeugt das String-Objekt str4 mit dem Wert net und einer Länge length von 3. Dieses Ergebnis wird beginnend an Offset 5 in chArray und über einen Zähler von 3 Zeichen ermittelt. (Diese Methode ist im Ergebnis identisch mit der valueOf()-Methode, die ein char-Array, einen Offset und einen Zähler als Parameter entgegennimmt.)
Der hier folgende letzte Konstruktor nimmt einen StringBuffer als Parameter entgegen:
String str5 = new(myStringBuff);
Er erzeugt das String-Objekt str5 mit dem Inhalt von myStringBuff.
Der Zugriff auf die Elemente eines Strings erfolgt auf dieselbe Weise wie der Zugriff auf Array-Elemente, und Sie können Integer oder Integer-Ausdrücke als Indizes verwenden, die auf die einzelnen Elemente des String-Arrays verweisen. Wenn ein String-Index zur Laufzeit außerhalb der Grenzen des Arrays liegt, wirft der Java-Interpreter eine StringIndexOutOfBoundsException auf.
Hier folgt ein Beispiel für eine for-Schleife, die alle Werte eines Strings mit dem Zeichen A initialisiert, indem sie auf die einzelnen Array-Elemente zugreift:
String aString[] = new String[3];
for (int i = 0; i < aString.length; i++)
aString[i] = 'A';
Neben dem Zugriff auf die Zeichen als Array-Elemente definiert die String-Klasse mehrere Methoden, die den Zugriff auf die Werte in Strings vereinfachen sollen. Die Klasse AccessString in Listing 2.1 zeigt einige dieser Methoden.
Listing 2.1: AccessString.java
1: class AccessString {
2: public static void main(String args[]) {
3: String str = "Time is too slow for those who wait.";
4: System.out.println();
5:
6: System.out.println("The string is: " + str);
7:
8: System.out.println("Length of string: "
9: + str.length());
10:
11: System.out.println("Character at position 17: "
12: + str.charAt(17));
13:
14: System.out.println("String begins with \"those\": "
15: + str.startsWith("those"));
16:
17: System.out.println("String ends with \"wait.\": "
18: + str.endsWith("wait."));
19:
20: System.out.println("Index of the first \"w\" character: "
21: + str.indexOf('w'));
22:
23: System.out.println("Index of the last \"w\" character: "
24: + str.lastIndexOf('w'));
25:
26: System.out.println("Substring from 0 to 2: "
27: + str.substring(0, 3));
28:
29: }
30: }
Nachdem Sie dieses Programm kompiliert und ausgeführt haben, sehen Sie die folgende Ausgabe im Ausführungsprotokoll-Fenster:
String str = "Hello";
String loStr = str.substring(3)); // die richtige Methode
Wenn Sie versuchen, die erste Form zu verwenden, um diese Aufgabe auszuführen, müßten Sie einen Anfang inklusive Index 3, aber ein Ende exklusive Index 5 angeben. Das wirft aber eine Ausnahme auf, weil die gültigen Indizes für str nur von 0 bis 4 reichen.
Es gibt noch andere Methoden, die mehrere Formen besitzen (wie etwa indexOf() oder lastIndexOf()), aber für dieses Beispiel wurde für jede Methode nur eine Form gezeigt. Detailliertere Informationen entnehmen Sie der Online-Dokumentation für das String-Objekt.
Die String-Klasse bietet Methoden für den Vergleich und die Manipulation von Strings. Wenn Objekte verglichen werden, wird ein Boolescher Wert zurückgegeben, der angibt, ob die beiden Objekt-Variablen auf dasselbe Objekt im Speicher verweisen. Wenn Sie zwei String-Literale mit demselben Wert definieren, verweisen diese auf dieselbe Speicherstelle.
Angenommen, Sie haben new verwendet, um zwei separate Strings zu erzeugen, wie können Sie diese dann vergleichen? Die Klasse String definiert drei Vergleichsmethoden für diesen Zweck. Die erste Methode, equals(), berücksichtigt die Groß-/Kleinschreibung, vergleicht die entsprechenden Zeichen aus beiden Strings und ergibt true, wenn sie identische Werte aufweisen. Darüber hinaus definiert die Klasse String auch noch eine Version, die die Groß-/Kleinschreibung nicht berücksichtigt, equalsIgnoreCase(), die die entsprechenden Zeichen ohne Rücksicht darauf vergleicht, ob es sich um Groß- oder Kleinbuchstaben handelt.
Eine dritte Vergleichsmethode, compareTo(), vergleicht zwei Strings und gibt einen Integerwert zurück, der den numerischen Unterschied zwischen beiden zurückgibt:
Die Integer-Unicode-Werte der jeweiligen Zeichenpaare werden verglichen, bis eines gefunden wird, dessen Werte nicht übereinstimmen (die weiteren Zeichen werden ignoriert). In diesem Beispiel stimmt das erste Zeichenpaar, J und C, nicht überein, und weil die Methode von firstStr aufgerufen wird, subtrahiert die compareTo()-Methode den Integerwert für C (99) von dem für J (106). Das Ergebnis ist 7, ein positiver Integer, der anzeigt, daß JBuilder größer als C++Builder ist (wenigstes hinsichtlich des Strings). Ein negativer Integer weist darauf hin, daß der aufrufende String kleiner als der übergebene String ist. Null zeigt an, daß die beiden Strings gleich sind.
Strings, von denen Sie wissen, daß sie irgendwann manipuliert werden, sollten zwar in StringBuffer-Objekten untergebracht werden, aber die Klasse String definiert auch ein paar Techniken zum Ändern von String-Objekt-Werten. Sie können das Ergebnis jedoch nicht dem ursprünglichen String zuweisen, sondern Sie müssen einen neuen String definieren, der das Ergebnis dieser Methoden aufnimmt. Unter anderem handelt es sich um concat(), replace(), trim(), toLowerCase() und toUpperCase().
Die Klasse ChangeString in Listing 2.2 zeigt die Verwendung einiger dieser Methoden.
Listing 2.2: ChangeString.java
1: class ChangeString {
2: public static void main(String args[]) {
3: System.out.println();
4:
5: String aStr = " Geed ";
6: String bStr = "Merning!";
7: String cStr = aStr.concat(bStr);
8: System.out.println(cStr);
9:
10: String dStr = cStr.replace('e', 'o');
11: System.out.println(dStr);
12:
13: String eStr = dStr.toLowerCase();
14: System.out.println(eStr);
15:
16: String fStr = eStr.toUpperCase();
17: System.out.println(fStr);
18:
19: String gStr = fStr.trim();
20: System.out.println(gStr);
21:
22: }
23: }
Nachdem Sie das Programm kompiliert und ausgeführt haben, sollten Sie die folgende Ausgabe erhalten:
Beachten Sie, daß Java für die Stringkonkatenation den Konkatenations-Operator (+) bereitstellt.
In java.lang.String sind noch weitere Methoden definiert, die hier nicht beschrieben wurden, unter anderem copyValueOf(), getBytes(), hashCode(), intern() und regionMatches(). Detailliertere Informationen entnehmen Sie bitte der Online-Java-Referenz des JBuilder.
Für ein StringBuffer-Objekt gibt es neben der Länge length, die angibt, wie viele Zeichen es enthält, auch die Methode capacity, die angibt, für wie viele Zeichen der Puffer alloziert wurde. Um ein StringBuffer-Objekt zu erzeugen, verwenden Sie einen der drei Klassen-Konstruktoren, z.B.:
StringBuffer myStrBuff = new StringBuffer(myString);
Dieser Konstruktor nimmt einen String-Parameter entgegen. Er erzeugt den StringBuffer myStrBuff, der den Inhalt eines Strings namens myString beinhaltet, dessen Länge gleich dem Wert length von myString gesetzt wird. capacity wird dynamisch alloziert. Für den String-Parameter können Sie auch ein String-Literal verwenden.
Dieser Konstruktor nimmt keine Parameter entgegen und ist der Standard:
StringBuffer myStringBuff = new StringBuffer();
Er erzeugt den StringBuffer myStringBuff mit einer length von Null. Auch hier wird capacity dynamisch alloziert.
Der folgende Konstruktor nimmt einen Integer-Parameter entgegen, der length und capacity des StringBuffer darstellt:
StringBuffer aBuff = new StringBuffer(25);
Dieses Beispiel erzeugt den StringBuffer aBuff mit einer zunächst statisch allozierten capacity von 25, initialisiert mit Leerzeichen und einer length von 0.
Der Zugriff auf einzelne Elemente von StringBuffer-Objekten kann unter Verwendung von Indizes erfolgen, so wie in allen anderen Arrays. Weil StringBuffer jedoch wirklich Objekte sind, definiert die Klasse StringBuffer eigene Methoden, die die Anzahl der Zeichen im Puffer, die Anzahl der Zeichen, die der Puffer aufnehmen kann, sowie das Zeichen an einer bestimmten Position zurückgeben können. Die Klasse AccessBuffer aus Listing 2.3 demonstriert diese Methoden.
Listing 2.3: AccessBuffer.java
1: class AccessBuffer {
2: public static void main(String args[]) {
3:
4: System.out.println();
5: StringBuffer aBuff = new StringBuffer("Time Flies!");
6: System.out.println("The contents of aBuff: " + aBuff);
7: System.out.println("Capacity: " + aBuff.capacity());
8: System.out.println("Length: " + aBuff.length());
9: System.out.println("Character at position 7: "
10: + aBuff.charAt(7));
11:
12: System.out.println();
13: StringBuffer bBuff = new StringBuffer();
14: System.out.println("The contents of bBuff: " + bBuff);
15: System.out.println("Capacity: " + bBuff.capacity());
16: System.out.println("Length: " + bBuff.length());
17:
18: System.out.println();
19: StringBuffer cBuff = new StringBuffer(25);
20: System.out.println("The contents of cBuff: " + cBuff);
21: System.out.println("Capacity: " + cBuff.capacity());
22: System.out.println("Length: " + cBuff.length());
23: }
24: }
Wenn Sie das Programm kompilieren und ausführen, sollten Sie die folgende Ausgabe erhalten:
The contents of aBuff: Time flies!
Capacity: 27
Length: 11
Character at position 7: i
The contents of bBuff:
Capacity: 16
Length: 0
The contents of cBuff:
Capacity: 25
Length: 0
Die Klasse StringBuffer stellt mehrere Methoden zur Manipulation des Pufferinhalts zur Verfügung. Unter anderem handelt es sich dabei um setLength(), setCharAt(), append(), insert(), reverse() und toString(). Im Gegensatz zu String-Objekten, wo das Ergebnis einer String-Manipulation einer zweiten String-Variablen zugewiesen werden muß, kann ein StringBuffer-Objekt das Ergebnis der Methode direkt dem aufrufenden StringBuffer zuweisen. Die Klasse ChangeBuffer in Listing 2.4 zeigt diese Methoden zur Manipulation von Puffern, die in der Klasse StringBuffer definiert sind.
Listing 2.4: ChangeBuffer.java
1: class ChangeBuffer {
2: public static void main(String args[]) {
3: System.out.println();
4:
5: StringBuffer aBuff = new StringBuffer("Time plies!");
6: System.out.println("aBuff contents: " + aBuff);
7: System.out.println("Capacity, length: "
8: + aBuff.capacity() + ", "
9: + aBuff.length());
10: aBuff.setLength(10);
11: System.out.println("aBuff contents: " + aBuff);
12: System.out.println("Capacity, length: "
13: + aBuff.capacity() + ", "
14: + aBuff.length());
15: aBuff.setCharAt(5, 'f');
16: System.out.println("aBuff contents: " + aBuff);
17: System.out.println("Capacity, length: "
18: + aBuff.capacity() + ", "
19: + aBuff.length());
20: aBuff.append(" having fun!");
21: System.out.println("aBuff contents: " + aBuff);
22: System.out.println("Capacity, length: "
23: + aBuff.capacity() + ", "
24: + aBuff.length());
25: aBuff.insert(11, "when you're ");
26: System.out.println("aBuff contents: " + aBuff);
27: System.out.println("Capacity, length: "
28: + aBuff.capacity() + ", "
29: + aBuff.length());
30: String aStr = aBuff.toString();
31: System.out.println("The string is: " + aStr);
32: System.out.println("Length: " + aStr.length());
33:
34: StringBuffer bBuff = new StringBuffer("Bob//");
35: System.out.println("Original bBuff contents: " + bBuff);
36: bBuff.reverse();
37: System.out.println("Reversed bBuff contents: " + bBuff);
38: }
39: }
Nachdem Sie das Programm kompiliert und ausgeführt haben, sollten Sie die folgende Ausgabe sehen:
Hier wurden nicht alle in java.lang.StringBuffer definierten Methoden gezeigt, beispielsweise ensureCapacity() und getChars(). Weitere Informationen über diese Methoden entnehmen Sie der Online-Java-Referenz von JBuilder.
Sie könnten mit dem, was Sie bisher gelernt haben, schon Java-Programme schreiben, die jedoch nur wenig sinnvoll wären. Programme sowohl in Java als auch in anderen Programmiersprachen werden erst durch Steuerkonstrukte (Schleifen und Bedingungen) für den praktischen Einsatz geeignet. Diese Konstrukte sorgen dafür, daß basierend auf logischen Tests unterschiedliche Programmteile ausgeführt werden.
Die if-else-Bedingung, die Ihnen ermöglicht, basierend auf einer einfachen Überprüfung verschiedene Codezeilen auszuführen, ist fast identisch mit den if-else-Anweisungen anderer Sprachen. Und hier die Syntax:
if bedingung
anweisung(en);
else anweisung(en);
Dem Schlüsselwort if folgt die bedingung, das ist eine Boolesche Auswertung. Der bedingung folgen unmittelbar anweisung(en) (entweder eine einzelne Anweisung oder ein Anweisungsblock), die ausgeführt werden, wenn die bedingung true ergibt. Das optionale Schlüsselwort else stellt die anweisung(en) zur Verfügung, die ausgeführt werden, wenn die bedingung false ist:
if (x < y)
System.out.println("x ist kleiner als y");
else System.out.println("x ist größer oder gleich y");
Hier folgt ein Beispiel für eine if-else-Bedingung, die einen Block im else-Teil der Anweisung verwendet:
if (engineState == true)
System.out.println("Motor läuft bereits.");
else {
System.out.println("Versuchen Sie, den Motor zu starten.");
if (gasLevel >= 1)
engineState = true;
else System.out.println("Kein Benzin - der Motor kann nicht gestartet werden!");
}
Dieses Beispiel verwendet in der ersten if-Anweisung die Prüfung (engineState == true), was eigentlich redundant und damit ein unnötiger Vergleich ist. Weil es sich bei engineState um eine Boolesche Variable handelt, können Sie einfach den Wert der Variablen verwenden, statt diesen mit true zu vergleichen:
if (engineState)
System.out.println("Motor läuft.");
else System.out.println("Motor steht.");
Wenn Sie if-else-Anweisungen verschachteln, müssen Sie manchmal unterscheiden, zu welchem if das else gehört. Hier ein Beispiel:
if (bedingung1)
if (bedingung2)
anweisung;
else anweisung;
In diesem Beispiel sieht der Leser anhand der Einrückung, daß das else zur ersten if-Anweisung gehört und ausgeführt werden sollte, wenn bedingung1 gleich false ist. Leider ist der Java-Compiler nicht so scharfsinnig. Er nimmt an, daß das else zu der if-Anweisung gehört, die ihm unmittelbar vorausgeht. Im obigen Beispiel würde er also annehmen, daß das else zum zweiten if gehört und ausgeführt wird, wenn bedingung2 false ergibt. Und so sorgen Sie dafür, daß Java die richtige Zuordnung trifft:
if (bedingung1) {
if (bedingung2)
anweisung;
}
else anweisung;
Auch wenn die zweite if-Anweisung keinen Block darstellt, können Sie die geschweiften Klammern ({}) verwenden, um das ganze zu erzwingen und seinen Gültigkeitsbereich so anzugeben, als handelte es sich um einen Block. Das verhindert, daß das else sich dem zweiten if zuordnet, weil durch den Gültigkeitsbereich dieser neue Block unsichtbar für Anweisungen außerhalb des Blocks wird.
Eine Alternative zu der if-else-Anweisung in einer bedingten Anweisung ist der Bedingungsoperator, ein ternärer Operator.
Der Bedingungsoperator ist ein Ausdruck, d.h. er gibt einen Wert zurück (anders als die if-else-Anweisung, die einfach nur die Ausführung steuert). Der Bedingungsoperator ist vor allem für sehr kurze oder einfache Bedingungen sinnvoll.
Die Syntax für den Bedingungsoperator lautet:
bedingung ? true-ergebnis : false-ergebnis;
Bei der bedingung handelt es sich um einen Boolesche Ausdruck, der true oder false zurückgibt, ähnlich der bedingung in der if-else-Anweisung. Wenn die bedingung gleich true ist, gibt der Bedingungsoperator-Ausdruck den Wert von true-ergebnis zurück, ist sie gleich false, gibt er den Wert von false-ergebnis zurück.
Hier folgt ein Beispiel für eine Bedingung, die die Werte von x und y überprüft, den kleineren von beiden zurückgibt und diesen Wert der Variablen smaller zuweist:
Der Bedingungsoperator hat eine sehr geringe Priorität. Die einzigen Operatoren, die eine noch geringere Priorität haben, sind die Zuweisungsoperatoren. Der Bedingungsoperator wird deshalb im allgemeinen erst ausgewertet, nachdem alle seine Unter-Ausdrücke ausgewertet sind. Weitere Informationen über die Prioritäten von Operatoren finden Sie in Tabelle 2.6.
Hier folgt die Auswertungsreihenfolge des obigen Beispiels:
Und hier derselbe Vergleich unter Verwendung der if-else-Anweisung:
int smaller;
if x < y
smaller = x;
else smaller = y;
Wie Sie aus diesen Beispielen sehen, können Sie unter Verwendung des Bedingungsoperators mit einer einzigen Codezeile sehr viel bewerkstelligen. Sie können jedoch keine Zuweisungen oder Block-Anweisungen als Operanden verwenden. Wenn Sie diese komplexeren Anweisungen brauchen, müssen Sie die if-else-Anweisung verwenden.
In allen Programmiersprachen ist es üblich, eine Variable mit einem Wert aus einer Wertemenge zu vergleichen und basierend auf dem Ergebnis unterschiedliche Aktionen auszuführen. Wenn Sie nur if-else-Anweisungen einsetzen, wird das sehr schnell unübersichtlich, abhängig von der Formatierung und dem Umfang der Wertemenge, für die die Vergleiche ausgeführt werden. Sie könnten beispielsweise eine if-else-Anweisung wie die folgende ausführen:
if (oper == '+')
addargs(arg1, arg2);
else if (oper == '-')
subargs(arg1, arg2);
else if (oper == '*')
multargs(arg1, arg2);
else if (oper == '/')
divargs(arg1, arg2);
Diese Form einer if-else-Anweisung wird auch als verschachteltes if-else bezeichnet, weil jede Anweisung wiederum ein weiteres if-else enthält usw. In diesem Beispiel werden vier Möglichkeiten abgetestet, deshalb ist der Code noch relativ übersichtlich. Weil diese Situation jedoch so häufig auftritt, gibt es einen speziellen Bedingungsausdruck, der sie verarbeitet: switch, manchmal auch als case-Anweisung bezeichnet.
Hier die Syntax für die switch-Bedingung:
switch (ausdruck) {
case konstante_1;
anweisung(en);
break;
case konstante_2;
anweisung(en);
break;
...
default:
anweisung(en);
}
Die switch-Struktur besteht aus mehreren case-Werten (konstante_1, konstante_2 usw.) und einer optionalen default-Anweisung. Der ausdruck (der einen elementaren Typ wie byte, char, short oder int zurückgeben muß), wird mit jedem der case-Werte verglichen. Wenn eine Übereinstimmung festgestellt wird, werden die anweisung(en) für den entsprechenden case-Wert ausgeführt, bis eine break-Anweisung erkannt wird oder das Ende der switch-Anweisung erreicht ist. Nachdem Sie alle case-Werte überprüft haben und keine Übereinstimmung festgestellt wurde, wird die default-Anweisung ausgeführt. Die Angabe der default-Anweisung ist optional, wenn sie also nicht verwendet wird und es keine Übereinstimmung mit einem der case-Werte gibt, wird die switch-Anweisung beendet, ohne irgendeine Aktion vorgenommen zu haben.
Wenn nun jemand anderer den Code nach Wochen (oder Monaten) betrachtet, erkennt er sofort, daß es Absicht war, für die default-Anweisung keine Aktion auszuführen.
Und hier die verschachtelte if-else-Anweisung von vorhin, dargestellt als switch-Anweisung:
switch (oper)
case '+';
addargs(arg1, arg2);
break;
case '-';
subargs(arg1, arg2);
break;
case '*';
multargs(arg1, arg2);
break;
case '/';
divargs(arg1, arg2);
break;
default: /* nichts tun */ ;
}
Beachten Sie die break-Anweisung am Ende der einzelnen case-Fälle. Ohne das explizite break werden, nachdem eine Übereinstimmung gefunden ist, die Anweisungen für diese Übereinstimmung und alle weiteren Anweisungen in dem switch-Block ausgeführt, bis entweder ein break oder das Ende des Blocks erkannt wird (je nachdem, was zuerst auftritt). Normalerweise ist das nicht das gewünschte Verhalten, Sie sollten also sicherstellen, das break anzugeben, um einzugrenzen, welche Anweisungen bei einer Übereinstimmung ausgeführt werden sollen.
Andererseits kann diese Eigenschaft ganz nützlich sein. Betrachten Sie den Fall, wo Sie wollen, daß für mehrere case-Werte dieselben Anweisungen ausgeführt werden. Die Angabe immer wieder derselben Anweisung wäre redundant, deshalb bietet Java die Möglichkeit, daß für mehrere case-Werte dieselben Anweisungen ausgeführt werden. Durch Weglassen der Ergebnis-Anweisung für einen case-Wert »fällt« die Ausführung durch zum nächsten case-Wert, und von dort zum nächsten, bis eine Anweisung gefunden ist. Hier ein Beispiel für diese Art Konstrukt:
switch (x) {
case 2:
case 4:
case 6:
case 8:
System.out.println("x ist eine gerade einstellige Zahl");
break;
default:
System.out.print("x ist keine gerade einstellige Zahl");
}
Die wesentliche Einschränkung des switch in Java ist, daß diese Tests und Werte sich nur auf einfache elementare Datentypen beziehen können (und dabei nur die elementaren Datentypen, die in einen int umgewandelt werden können). Sie können keine größeren elementaren Typen (long, float, double), Strings oder andere Objekte in einer switch-Anweisung verwenden, und Sie können auch nur auf Gleichheit testen. Dadurch ist switch wirklich nur für die einfachsten Vergleiche sinnvoll. Verschachtelte if-else-Anweisungen dagegen können für beliebige Tests und beliebige Wertetypen eingesetzt werden.
Die for-Schleife prüft eine Bedingung. Wenn diese Bedingung erfüllt ist, also true ergibt, wird eine Anweisung oder ein Anweisungsblock wiederholt ausgeführt, bis die Bedingung false wird. Diese Art Schleife wird häufig für einfache Iterationen verwendet, in denen Sie einen Anweisungsblock mit einer bestimmten Anzahl von Wiederholungen ausführen und dann beenden, aber Sie können for-Schleifen auch für jede andere Aufgabe einsetzen.
Hier die Syntax für die for-Schleife:
for (initialisierung; test; inkrement) anweisung(en);
Der Anfang der for-Schleife besteht aus drei Teilen:
Der anweisung(en)-Teil der for-Schleife stellt die Anweisung oder den Anweisungsblock dar, die immer dann ausgeführt werden, wenn test true ergibt und die Schleife durchlaufen wird. Hier ein Beispiel für eine for-Schleife, die alle Werte eines String-Arrays mit Null-Strings initialisiert:
String strArray[] = new String[10];
for (int i = 0; i < strArray.length; i++)
strArray[i] = "";
Jede dieser Komponenten der for-Schleife kann leere Anweisungen enthalten, d.h. Sie können einfach ein Semikolon ohne Ausdruck oder Anweisung einfügen, und dieser Teil der for-Schleife wird dann ignoriert. Beachten Sie, daß Sie bei der Verwendung einer leeren Anweisung in Ihrer for-Schleife vielleicht die Initialisierung oder Inkrementierung von Schleifenvariablen oder Schleifeninidzes an anderer Stelle selbst vornehmen müssen.
Sie können auch eine leere Anweisung für den Rumpf Ihrer for-Schleife angeben, wenn alles, was Sie bewerkstelligen wollen, bereits im Schleifenanfang passiert. Das folgende Beispiel zeigt, wie die erste Primzahl größer 4000 gefunden wird. Auch hier ist es am besten, wenn Sie eine Kommentaranweisung einfügen, wo Sie eine leere Anweisung verwenden, damit offensichtlich wird, daß das bewußt so realisiert wurde:
for (i = 4001; notPrime(i); i += 2)
/* nichts tun */ ;
Achten Sie auf die Plazierung des Semikolons (;) nach der ersten Zeile in der for-Schleife. Betrachten Sie den folgenden Codeausschnitt:
for (int i = 0; i < 10; i++);
System.out.println("Schleife!");
Hier wollte man, daß der String Schleife! 10mal ausgegeben wird, aber statt dessen wird die Schleife nur 10mal durchlaufen und macht nichts außer Testen und Inkrementieren, um dann nur einmal Schleife! auszugeben. Warum? Aufgrund des fehlerhaft plazierten Semikolons (;) am Ende der ersten Zeile der for-Schleife.
Die while-Schleife wird genutzt, um eine Anweisung oder einen Anweisungsblock so lange zu wiederholen, wie eine bestimmte Bedingung true ist.
Hier die Syntax der while-Schleife:
while (bedingung) anweisung(en);
Die bedingung ist ein Boolescher Ausdruck, der ein Boolesches Ergebnis zurückgibt. Wenn er true zurückgibt, führt die while-Schleife die anweisung(en) aus und prüft dann die Bedingung erneut ab, bis die Bedingung false ergibt. Wenn die Bedingung beim ersten Prüfen false ist, werden die anweisung(en) der while-Schleife nicht ausgeführt.
Hier folgt ein Beispiel für eine while-Schleife, die die Elemente eines Arrays mit Integern (in arrInt) in ein Array mit Fließkommazahlen (in arrFloat) kopiert und dabei jedes Element in eine Fließkommazahl umwandelt. Um das Ganze interessanter zu machen, gibt es zwei Bedingung, die true sein müssen, damit die Schleife ausgeführt wird:
Um das zu bewerkstelligen, erfolgt in der Schleife ein zusammengesetzter Test, der mehrere Bedingungen auswertet. Bei der Verwendung des Operators && müssen beide Teile true sein, damit die Bedingung true ergibt. Diese Schleife verwendet außerdem das Postfix-Inkrement (++), um den Zähler nach jedem Schleifendurchgang zu erhöhen:
int count = 0;
while ( (count < arrInt.length) && (arrInt[count] != 0) ) {
arrFloat[count] = (float) arrInt[count];
count++;
}
Angenommen, arrInt hat die Länge 20. Wenn keiner der Werte aus arrInt 0 ist, würde diese while-Schleife 20mal ausgeführt, weil die Prüfung true ergibt, während die count-Variable bei der Iteration von 0 bis 19 true ergibt. Ist dagegen einer der arrInt-Werte 0, wird die Schleife zwischen 0- und 19mal ausgeführt, abhängig von der Position der ersten 0 in dem arrInt.
Bei der Beschreibung der while-Schleife haben wir darauf hingewiesen, daß sie möglicherweise überhaupt nicht ausgeführt wird, wenn die Bedingung gleich bei der ersten Auswertung false ergibt. Wenn Sie wollen, daß die Schleife mindestens einmal ausgeführt wird, brauchen Sie eine do-while-Schleife. Sie macht im wesentlichen dasselbe wie eine while-Schleife, mit der Ausnahme, daß die Anweisungen zuerst ausgeführt werden, und dann die Bedingung ausgewertet wird.
Hier die Syntax für die do-while-Schleife:
do anweisung(en) while bedingung;
Hier werden zuerst die anweisung(en) ausgeführt und anschließend wird die bedingung ausgewertet. Wenn die bedingung true ergibt, werden die anweisung(en) erneut ausgeführt, ergibt sie false, wird die Schleife beendet. Hier ein Beispiel, das 20 Zeilen ausgibt:
int x = 1;
do {
System.out.println("Schleifendurchgang " + x);
x++;
} while (x <= 20);
Merken Sie sich, daß do-while-Schleifen immer mindestens einmal ausgeführt werden und die Prüfung am Ende ausführen, während while-Schleifen am Anfang prüfen und möglicherweise nie ausgeführt werden.
Sie haben break bereits als Teil der switch-Anweisung kennengelernt; es beendet die Ausführung von switch und das Programm wird mit der Anweisung hinter dem switch-Konstrukt fortgesetzt. In einer Schleife macht das Schlüsselwort break dasselbe - es bricht die Ausführung der aktuellen Schleife unmittelbar ab. Wenn Sie verschachtelte Schleifen verwenden, wird die Ausführung in der nächsten äußeren Schleife fortgesetzt. Andernfalls setzt das Programm seine Ausführung einfach in der nächsten Anweisung hinter der Schleife fort.
Betrachten Sie beispielsweise die folgende while-Schleife, die Elemente von einem Integer-Array in ein Fließkommazahlen-Array kopiert, bis das Ende des Arrays erreicht oder eine 0 erkannt wird. Den letzteren Fall können Sie innerhalb des Rumpfs von wihle prüfen und die Schleife gegebenenfalls mit break verlassen:
int count = 0;
while (count < arrInt.length) {
if (arrInt[count] == 0)
break;
arrFloat[count] = (float) arrInt[count];
count++;
}
Die break-Anweisung bewirkt, daß die Schleife beendet wird, sobald die Bedingung erfüllt ist. In diesem Fall wird die Schleife beendet, wenn einer der arrInt-Werte gleich 0 ist. Im Gegensatz dazu beendet das Schlüsselwort continue die Ausführung des aktuellen Schleifendurchgangs, und führt den nächsten Schleifendurchgang aus. Bei do-while-Schleifen bedeutet das, daß die Schleife die Ausführung oben wieder beginnt, while- und for-Schleifen werden mit der Auswertung der Bedingung fortgesetzt.
Das Schlüsselwort continue ist praktisch, wenn Sie die Schleife neu starten wollen, ohne die darin enthaltenen Anweisungen auszuführen. Betrachten Sie das obige Beispiel, wo ein Array in ein anderes kopiert wird. Sie können prüfen, ob das aktuelle Integer-Element gleich 0 ist. In diesem Fall können Sie die Schleife neu starten, so daß das resultierende Fließkommazahlen-Array keinen Null-Wert entgegennehmen muß. Beachten Sie, daß Sie möglicherweise einige Elemente im ersten Array überspringen, Sie nun zwei Array-Zähler verwalten müssen:
int iCount = 0;
int fCount = 0;
while (iCount < arrInt.length) {
if (arrInt[iCount] == 0) {
iCount++;
continue;
}
arrFloat[fCount++] = (float) arrInt[iCount++];
}
Dieses Beispiel durchläuft beide Arrays, und kopiert den Integer-Wert aus arrInt nur dann in arrFloat, wenn das Element in arrInt ungleich Null ist.
Für break und continue kann optional ein Sprungziel angegeben werden, das Java mitteilt, mit welcher Programmanweisung es fortfahren soll. Ohne ein Sprungziel (Label) setzt break die Ausführung mit der nächsten Programmanweisung nach der schließenden Schleife fort und continue startet die umschließende Schleife neu. Die Verwendung von break und continue mit Sprungzielen ermöglicht Ihnen, eine Schleife außerhalb der aktuellen Schleife fortzusetzen oder mehrere Schichten verschachtelter Schleifen gleichzeitig zu verlassen.
Um eine Schleife mit Sprungzielen zu verwenden, fügen Sie das Label vor der Schleifeninitialisierung ein. Hinter dem Label wird kein Semikolon (;), sondern ein Doppelpunkt (:) angegeben. Wenn Sie jetzt break oder continue verwenden, fügen Sie nach dem Schlüsselwort das Label, also das Sprungziel ein:
out:
for (int i = 0; i < 10; i++) {
while (x < 50) {
if (i * x == 400)
break out;
...
} // Ende der while-Schleife
...
} // Ende der for-Schleife
... // Hier wird die Ausführung nach break out fortgesetzt
In diesem Codeabschnitt kennzeichnet out: den äußeren Block. Innerhalb der for- und der while-Schleife bewirkt die break-Anweisung für eine bestimmte Bedingung, daß beide Schleifen verlassen und die Ausführung mit der Codezeile hinter der for-Schleife fortgesetzt wird.
Das Programm Breakers in Listing 2.5 ist ein Beispiel für verschachtelte for-Schleifen und ein break mit Sprungziel. In der inneren Schleife werden beide for-Schleifen gleichzeitig verlassen, wenn die Summe der beiden Zähler größer vier ist.
1: class Breakers {
2: public static void main(String args[]) {
3:
4: ers:
5: for (int i = 1; i <= 5; i++) {
6: for (int k = 1; k <= 3; k++) {
7: System.out.println("i ist " + i + ", k ist " + k);
8: if ((i + k) > 4)
9: break ers;
10: }
11: }
12: System.out.println("Beide Schleifen beendet");
13: }
14: }
Wenn Sie das Programm ausführen, sehen Sie die folgende Ausgabe:
Heute haben Sie zahlreiche Aspekte zu der Sprache kennengelernt, die JBuilder zugrunde liegt, Java. Sie haben Programmanweisungen kennengelernt und unter anderem auch die speziellen Dokumentations-Kommentare von Java. Datentypen, Variablen und Literale wurden vorgestellt. Darüber hinaus haben Sie die Operatoren gesehen, die in Java zur Verfügung stehen, und wie sie unter Berücksichtigung der Prioritäten in Java angewendet werden. Es wurde darauf hingewiesen, daß Java zahlreiche komplexe mathematische Funktionen bietet.
Sie haben viel über Arrays, String und Stringpuffer gelernt. Sie haben gesehen, wie Array-, String- und StringBuffer-Variablen deklariert werden, und wie ihre Objekte erzeugt werden. Sie haben die Methoden zum Zugriff auf die Daten in diesen Strukturen kennengelernt und wissen, wo Sie zusätzliche Informationen finden können. Sie können mehrdimensionale Arrays erzeugen und verwenden und Sie wissen, wann anstelle von String-Objekten StringBuffer-Objekte verwendet werden sollten.
Es gab zwei Themenbereiche, die Ihnen sicher häufig in Ihren eigenen Java-Programmen begegnen werden, nämlich Bedingungen und Schleifen. Bedingungen sind unter anderem die if-else- und die switch-Anweisungen, mit denen Sie zu bestimmten Codezeilen verzweigen können, abhängig von dem Ergebnis einer Booleschen Auswertung. Die Schleifenanweisungen umfassen die for-, while- und do-while-Schleifen, die Ihnen ermöglichen, bestimmte Anweisungen wiederholt auszuführen, bis eine bestimmte Bedingung erfüllt ist.
Nachdem Sie diese Sprachkonstrukte kennengelernt haben, werden Sie lernen, Klassen zu deklarieren und Methoden zu erzeugen, mit deren Hilfe Instanzen dieser Klassen miteinander kommunizieren können.
F Ich habe keine Möglichkeit gesehen, lokale Konstanten zu erzeugen. Unterstützt Java keine Konstanten?
A Doch, aber Sie können in Java keine lokalen Konstanten erzeugen. Sie können nur Instanzkonstanten und Klassenkonstanten erzeugen. Mehr darüber erfahren Sie in der nächsten Lektion.
F Was passiert, wenn Sie einer Variablen einen numerischen Wert zuweisen, die zu groß (oder zu klein) für diese Variable ist?
A Sie denken vielleicht, die Variable wird einfach nur in den nächst größeren Typ umgewandelt, aber das ist so nicht der Fall. Wenn es sich bei dem Wert um eine positive Zahl handelt, entsteht ein »Überlauf« für die Variable, d.h. die Variable kippt um und wird zum kleinsten negativen Wert für diesen Typ, von wo aus weitergezählt wird. Ist der Wert eine negative Zahl, entsteht ein »Unterlauf« für die Variable, d.h. sie wird zum höchsten Wert für diesen Typ und zählt von dort aus abwärts. Das kann zu fehlerhaften Ergebnissen führen, stellen Sie also sicher, daß Sie für numerische Werte die jeweils richtigen Typen deklarieren. Wenn Sie Zweifel haben, verwenden Sie den nächst größeren Typ.
F Wenn Arrays Objekte sind und mit new erzeugt werden müssen, wo ist dann die Klasse Array? Ich habe sie in den Klassenbibliotheken von Java nicht gefunden.
A Arrays werden in Java einigermaßen kompliziert implementiert. Die Klasse Array wird automatisch angelegt, wenn Ihr Java-Programm ausgeführt wird, deshalb können keine Unterklassen davon angelegt werden. Array stellt die grundlegende Umgebung für Arrays bereit, unter anderem die Instanzvariable length. Darüber besitzt hat jeder elementare Typ und jedes Objekt eine implizite Unterklasse von Array, die ein Array dieser Klasse oder dieses Objekts darstellt. Wenn Sie ein neues Array-Objekt erzeugen, haben Sie vielleicht nicht wirklich eine Klasse, aber es verhält sich so, als ob das der Fall wäre.
F Sie haben gesagt, daß man die Methode arraycopy() auf Arrays anwenden kann, aber kann ich sie auch für Instanzen der Klassen String und StringBuffer verwenden, die doch ebenfalls als Arrays implementiert sind?
A Ja, mit einer Ausnahme. Weil String-Werte nicht direkt verändert werden können, können Sie keinen String als Ziel-Array verwenden. wenn dagegen das Ziel-Array oder der StringBuffer bereits im Speicher alloziert sind, können Sie arraycopy() verwenden, um Daten hineinzukopieren.
F Wenn ein Array Werte beliebiger elementarer Datentypen oder Objekte aufnehmen kann, wie kann ich dann genau ermittelt, wieviel Speicher ein Array alloziert?
A Um zu berechnen, wieviel Speicher für ein Array alloziert wird, multiplizieren Sie die Anzahl der Elemente mit der Anzahl der Bits, die für den Datentyp dieses Elements verwendet werden. Addieren Sie 32 Bit für das Feld length (das immer ein int ist). Teilen Sie durch 8, um die Anzahl der Bytes zu erhalten, die für das Array alloziert werden. Wenn Sie beispielsweise ein Array mit 6 double-Elementen deklarieren wollen, die je 64 Bit belegen, können Sie den erforderlichen Speicher wie folgt berechnen:
Es ergeben sich insgesamt 52 Byte Speicherbedarf.
F Sie haben in den Code-Listings von heute keine import-Anweisungen verwendet. Warum?
A Alle Methoden für Arrays, Strings und Stringpuffer sind Teil des Pakets java.lang, und die Klassen String und StringBuffer sind in java.lang.String bzw. java.lang.StringBuffer definiert. Weil das ganze java.lang-Paket implizit in alle Java-Programme importiert wird, ist es nicht erforderlich, eine explizite import-Anweisung anzugeben.
F Gibt es in Java eine goto-Anweisung?
A Java definiert das Schlüsselwort goto, aber es ist »reserviert« und wird momentan nicht verwendet. Aber ein break mit Sprungziel realisiert dasselbe.
F Ich habe eine Variable in einer Blockanweisung in einer if-else-Anweisung deklariert. Wenn das if-else abgearbeitet ist, verschwindet diese Variable. Was ist passiert?
A Blockanweisungen innerhalb geschweifter Klammern bilden einen lokalen Gültigkeitsbereich. Das bedeutet, wenn Sie eine Variable innerhalb eines Blocks deklarieren, ist sie nur innerhalb dieses Blocks sichtbar und verwendbar. Nachdem der Block abgearbeitet ist, stehen die Variablen, die dort deklariert wurden, nicht mehr zur Verfügung.
F Warum können in der switch-Anweisung keine String-Werte verwendet werden?
A Strings sind Objekte, und switch ist in Java nur für die elementaren Datentypen byte, char, short und int definiert. Um andere Typen zu vergleichen, müssen Sie verschachtelte if-else-Anweisungen verwenden, die allgemeinere Ausdrücke erlauben, unter anderem auch String-Vergleiche.
Der Workshop bietet zwei Möglichkeiten, zu überprüfen, was Sie in diesem Kapitel gelernt haben. Der Quiz-Teil stellt Ihnen Fragen, die Ihnen helfen sollen, Ihr Verständnis für den vorgestellten Stoff zu vertiefen. Die Antworten auf die Fragen finden Sie in Anhang A. Der Übungen-Teil ermöglicht Ihnen, Erfahrungen in der Anwendung der Dinge zu sammeln, die Sie hier kennengelernt haben. Versuchen Sie, diese Dinge durchzuarbeiten, bevor Sie mit der nächsten Lektion weitermachen.
int scores[] = new int[10];
int a = 3;
int b = 5;
scores[a-b];
char chArr[] = { 'I', 'n', 't', 'e', 'r', 'n', 'e', 't' };
String wwwStr = new String(chArr);
int wwwStrLength = wwwStr.length();
String firstStr = "Hier bin ich!"
String secondStr = "Nein, hier bin ich!!"
String thirdStr = "Hier bin ich!"
for (i = 0; i <= 100; i += 15);
System.out.println("Hidey, hidey, hidey, ho!");
Vorgegeben seien das folgende Integer-Array und die Strings:
int nums[] = {2, 10};
String aStr = "doesn't";
String bStr = "will get you that chicken across the road.";
String cStr = "make it"'
String dStr = "I say,"
String aSpace = " ";
Erzeugen Sie die Klasse Splat, die Array-Indizes verwendet, den Konkatenations-Operator (+) sowie die Methoden substring() und valueOf(), um einen String zu erzeugen, der wie folgt auf dem Bildschirm ausgegeben wird:
im neuen Programm durch die folgende Ausgabe ersetzt wurde:
Stellen Sie sicher, daß dies die einzige Änderung in der Ausgabe ist.