Kapitel 3 Anweisungen
Nach einem tabellarischen Überblick werden einige grundlegende Anweisungen besprochen. Schwerpunkt bilden Kontrollanweisungen, Deklarationen und Methoden-Aufrufe.
Da Java viele Anweisungen von C/C++ übernommen hat, ist es Ziel dieses Kapitels, die für Java typischen Details hervorzuheben.
Einzelne Anweisungen (Statements) werden durch ein Semikolon beendet und sind Kommandos, die die JVM ausführt.
Wie Ausdrücke können auch Anweisungen klassifiziert werden. In diesem Kapitel werden die grundlegenden und wichtigsten Anweisungen besprochen. Spezielle Details, wie etwa die Modifikatoren bei Deklarationen, werden erst im Rahmen der Vererbung ausführlich behandelt.
3.1 Überblick über Anweisungen ![down](common/down.gif) ![top](common/15x15leer.gif)
Zu der Gruppe der Anweisungen zählen insbesondere:
|
Deklarationen und Objekt-Anlage |
|
Ausdrücke und Zuweisungen (siehe auch Kapitel 2) |
|
Kontrollanweisungen, die den Programmablauf (Flow Control) beeinflussen |
|
Aufruf von Methoden |
|
Behandlung von Ausnahmen (Exceptions) |
|
Synchronisation von »nebenläufigen Prozessen« (Threads) |
Dieses Kapitel beschränkt sich auf die vier erstgenannten Arten von Anweisungen. Ausnahmen werden im siebten und Threads im neunten Kapitel vorgestellt.
Die nachfolgende Tabelle gibt einen kurzen Überblick über alle Anweisungen.
In der Spalte »Syntax« ist alles, was in eckigen Klammern steht, optional. Der Begriff Statement steht immer für eine einzelne Anweisung oder einen Block (siehe 3.2.2). In der Spalte »Beispiel« sind aus Platzgründen nur triviale Beispiele angegeben, deren Aufgabe einzig darin besteht, zusätzlich die Syntax zu verdeutlichen.
Tabelle 3.1 Übersicht der Anweisungen
Anweisung |
Beschreibung |
Syntax |
Beispiel |
leer |
leere Anweisung |
; |
|
Deklaration |
Deklarieren von Variablen eines Typs |
[modifiers] Type
var [=value], ...;
|
int[] iarr,
jarr= {0,1,2};
|
Ausdruck (mit Seiteneffekt) |
Zuweisung
Methoden-Aufruf
Objektanlage
In-/Dekrement |
var= expression;
method(args);
new Class(args)
++var; var--;
|
a>>>=1;
Math.floor(x);
new Test();
++c;
|
Block
(compound) |
Anweisungen, eingeschlossen in { } |
{ statement1 ...
statementN }
|
{ ++i; k= j%i;
d= k*k; }
|
markiert
(labeled) |
Anweisung mit vorstehender Marke |
labelId: statement
|
m1: while(true)
if (i==1) break m1;
|
return |
Beenden einer Methode |
return [expression];
|
return k*k+1;
|
if |
Wahl zwischen zwei Alternativen |
if (condition)
statement
[else statement]
|
if(b!=0) {
a=a/b; b+=b;
}
|
switch |
Wahl zwischen
mehreren
Alternativen |
switch (intExpr) { [case constVal:
[statement...]
[break;] ]...
[default:
[statement...] }
|
switch (i) {
case 0: s="0";
break;
case 1: s="1";
}
|
for |
Schleife (mittels Iteration) |
for(init;condition;
iteration)
statement
|
for (i=0; i<10;
i++)
n+=i;
|
while |
Schleife (aufgrund einer Anfangsbedingung) |
while (condition)
statement
|
while (i>0)
n+=i--;
|
do .. while |
Schleife (aufgrund einer Endbedingung) |
do { statement }
while (condition)
|
do { n+=--i; }
while (i>0);
|
break |
Springt aus Block |
break [label];
|
break m1;
|
continue |
Springt zur Schleifen- bedingung, -kopf |
continue [label];
|
continue outer;
|
try |
Behandelt Ausnahmen, die auftreten können,
oder führt zwingend Anweisungen aus |
try { statement }
[catch (EType eObj)
{ statement }]...
[finally
{ statement } ]
|
try { init();
}
catch
(Exception e)
{ System.out.
println(e);
}
|
throw |
Löst Ausnahmen aus |
throw throwableObj
|
synchronized |
Sperrmechanismus (locking) für
exklusiven Zugriff |
synchronized (obj)
{ sync_statement }
|
![Galileo Computing](common/jupiters.gif)
3.2 Grundlegende Anweisungen ![down](common/down.gif) ![top](common/top.gif)
Nachfolgend werden einfache Arten von Anweisungen vorgestellt, die Bestandteil selbst einfacher Programme sind.
3.2.1 Leere Anweisung ![down](common/down.gif) ![top](common/top.gif)
Die leere Anweisung besteht nur aus einem Semikolon und kann überall da eingesetzt werden, wo eine Anweisung syntaktisch erforderlich ist, aber nichts ausgeführt werden soll. Im folgenden Beispiel wird die leere Anweisung für eine Matrix-Initialisierung benutzt (zur for-Schleife siehe 3.3.2).
Beispiel
for-Schleife mit leerer Anweisung
int[][] imatrix= new int[100][100]; // 100x100 Matrix
for (int i= 0; i<imatrix.length; imatrix[i][i++]=1);
Erklärung: Die Elemente imatrix[0,0],...,imatrix[99,99] werden in der Schleife auf den Wert 1 gesetzt. Da die anderen Elemente bereits (implizit) mit 0 initialisiert wurden, ist somit imatrix eine Einheitsmatrix.
3.2.2 Block (Compound-Statement) ![down](common/down.gif) ![top](common/top.gif)
Einzelne Anweisungen können zu Blöcken (Blocks bzw. Compound-Statements) zusammengefasst werden.
Ein Block ist eine Anweisung
Ein Block besteht also aus null bis beliebig vielen Anweisungen, eingeschlossen in geschweifte Klammern. Syntaktisch kann er überall da stehen, wo eine einzelne Anweisung erforderlich ist.
Leerer Block
{} // ein Block: wirkt wie eine leere Anweisung
Beispiel
Hier wird die Matrix imatrix aus Beispiel 3.2.1 mit Hilfe zweier for-Schleifen auf der Konsole ausgegeben:
for (int i=0;i<imatrix.length;i++) { //
Block1-Start
for (int j=0;j<imatrix[i].length;j++) { // Block2-Start
System.out.print(imatrix[i][j]+" ");
} // Block2-Ende
System.out.println();
} // Block1-Ende
Erklärung: Block1 ist notwendig, sonst würde println() nicht zur Anweisung der äußeren for-Schleife gehören.
Block2 ist dagegen nicht notwendig, aber dann sinnvoll, wenn weitere Anweisungen zur inneren for-Schleife dazukommen könnten.
3.2.3 Ausdruck als Anweisung (Expression-Statement) ![down](common/down.gif) ![top](common/top.gif)
Folgende Ausdrücke bewirken neben der Operation einen Seiteneffekt:
|
Zuweisung |
|
Inkrementieren, Dekrementieren |
|
Methoden-Aufruf (siehe 3.5) |
|
Erschaffen einer Klassen-Instanz |
Werden diese Ausdrücke als Anweisungen verwendet, nennt man sie Expression-Statements.
Beispiele
a+= b;
i++; --j;
textFile.close(); // Aufruf der Methode close()
new Test(); // Erschaffen einer Instanz der
Klasse Test
3.3 Kontrollanweisungen ![down](common/down.gif) ![top](common/top.gif)
Kontrolle über den Ablauf von Anweisungen (Flow Control) gehört zur fundamentalen Grundlage von strukturierten Sprachen.
Java hat die Kontrollanweisungen in Syntax und Semantik von C/C++ mit nur wenigen Änderungen im Detail übernommen.1
Selektion,
Iteration, Sprunganweisung
Normalerweise werden Anweisungen sequenziell von oben nach unten ausgeführt. Mit Hilfe von Selektion, Iteration und Sprung kann die normale Abfolge verändert werden.
3.3.1 Selektion ![down](common/down.gif) ![top](common/top.gif)
Mit Hilfe der beiden Selektionsanweisungen if und switch ist es möglich, verschiedene Anweisungsblöcke aufgrund des Werts eines Ausdrucks auszuführen.
if-Anweisung
Die if-Anweisung gibt es in zwei Formen:
if (condition) trueStatement
oder
if (condition) trueStatement else falseStatement
Natürlich kann anstelle einer einzelnen Anweisung wie trueStatement ein Block stehen.
Wirkung von if
Die in Klammern stehende Bedingung ist ein logischer Ausdruck. Ist der Wert des Ausdrucks true, wird trueStatement ausgeführt, ist er false, wird in der ersten Form trueStatement ignoriert, d.h. übersprungen, und in der zweiten Form falseStatement ausgeführt.
if-Bedingung:
nur Typ boolean erlaubt
Im Gegensatz zu C/C++ erlaubt Java keine Interpretation von Zahlen als logische Werte:
int i=2;
if (i) System.out.println(1./i); // C-Fehler
if (i!=0) System.out.println(1./i); // ok
![Icon](common/R.gif)
Werden if-Anweisungen geschachtelt, wird die Zugehörigkeit der else-Zweige wie folgt festgelegt:
Zugehörigkeit
des else
Ein else-Teil gehört immer zum nächsten vorhergehenden if-Teil, dem noch kein else zugeordnet ist.
Beispiel
Hier eine »flache« nicht sehr lesbare if-Anweisung:
int i= -1;
if (i>=0) if (i>9) if (i==10) System.out.println(i);
else System.out.println("1.else");
else System.out.println("2.else");
Mit Hilfe der Regel soll die if-Anweisung benutzerfreundlich umgeschrieben werden:
if (i>=0)
if (i>9)
if (i==10) System.out.println(i);
else System.out.println("1.else");
else System.out.println("2.else");
Hier ist sofort erkennbar, dass beide println() übersprungen werden, wenn i den Wert -1 hat.
else-if-Leiter
Testen gleichrangiger
Bedingungen
Hat man aufgrund mehrerer Bedingungen unterschiedliche Anweisungen auszuführen, so verwendet man eine so genannte else-if-Leiter. Die Bedingungen werden gleichrangig auf derselben Stufe angeordnet:
if (condition1)
statement1
else if (condition2)
statement2
...
else if (conditionM)
statementM
[ else defaultStatement ]
Abbildung 3.1 if-else-Leiter
Ist eine der Bedingungen true, wird die zugehörige Anweisung ausgeführt und alle weiteren der Leiter übersprungen (siehe Abb. 3.1).
Beispiel
Im nachfolgenden Code-Fragment wird geprüft, ob der Wert der Zeichen-Variable c ein Umlaut oder ß ist, um dann den entsprechenden Zähler zu inkrementieren (daneben zeigt es auch die Faszination deutscher Variablen):
if (c=='ä' || c=='Ä') ä++;
// verstösst gegen die
else if (c=='ö' || c=='Ö') ö++; // Namenskonvention
für
else if (c=='ü' || c=='Ü') ü++; // Variable!
else if (c=='ß') ß++; // siehe Abschnitt 1.7
switch-Anweisung
Die switch-Anweisung hat die folgende Form:
switch (intExpression) {
case constExpression1: statementSequenz
[break;]
case constExpression2: statementSequenz
[break;]
...
case constExpressionM: statementSequenz
[break;]
[ default: statementSequenz ]
}
Abbildung 3.2 switch-Anweisung
Mit statementSequenz ist kein Block, sondern sind null bis beliebig viele Anweisungen gemeint.
Unter constExpression versteht man konstante Ausdrücke2 mit unterschiedlichen Werten, die sich implizit in den Typ int umwandeln lassen. Die break-Anweisung bzw. der default-Teil sind optional.
![Icon](common/R.gif)
Die Ausführung der Anweisung läuft wie folgt ab:
Die Ausführung der switch-
Anweisung
1. |
Der Wert der intExpression wird berechnet. |
2. |
Sollte er mit einem der Werte constExpression1,...,constExpressionM übereinstimmen, werden alle im switch folgenden Anweisungen (statementSequenz) ausgeführt, bis eine break-Anweisung auftritt. Diese beendet dann die switch-Anweisung. |
3. |
Stimmt keiner der Werte überein, wird – sofern vorhanden – die statementSequenz hinter default ausgeführt, ansonsten nichts. |
Beispiel
Die folgende switch-Anweisung variiert das if-Beispiel. Um die Wirkung von switch zu demonstrieren, wird c auf 'ä' gesetzt:
int ä=0,ü=0,ö=0,ß=0,z=0; char
c= 'ä';
switch (c) {
case 'ä': case 'Ä': ä++; // break;
case 'ö': case 'Ö': ö++; // break;
case 'ü': case 'Ü': ü++; // break;
case 'ß': ß++; // break;
default: if ('0'<=c && c<='9' ) z++;
}
System.out.print(ä+","+ö+","+ü+","+ß+","+z);
// :: 1,1,1,1,0
Erklärung: Das Zeichen c sowie die Zeichen-Literale werden in den Typ int umgewandelt (Punkt 1). Ohne break werden alle Zähler bis auf z hochgezählt (Punkt 2).
Entfernt man die Kommentare, wird nur ein Zähler hochgezählt, wobei das letzte break nur »gute Sitte« ist, da es in diesem Fall nicht notwendig ist.
Primitive Typen wie boolean, long, float oder double werden als Auswahlwerte hinter case nicht unterstützt.
3.3.2 Iterationen ![down](common/down.gif) ![top](common/top.gif)
while, do, for
Es gibt drei Iterations-Anweisungen: while, do und for. Die for-Schleife wird aufgrund ihrer Flexibilität sehr häufig verwendet und wurde ja auch bereits in mehreren Beispielen genutzt.
while-Anweisung
Die while-Anweisung hat die folgende Form:
while (condition)
statement
![Icon](common/R.gif)
Die Ausführung der while- Anweisung
Die in Klammern stehende Bedingung ist ein logischer Ausdruck. Die Ausführung der Anweisung läuft wie folgt ab:
Ist der Wert der Bedingung true, wird statement so lange ausgeführt, bis der Wert false ist. Dazu wird beim Eintritt in die Schleife und nach jeder Ausführung von statement der Bedingungsausdruck erneut berechnet und getestet.
Beispiel
Im folgenden Code-Fragment wird eine while-Schleife variiert, wobei der Variablen k eine zufällige Zahl zwischen 0..9 zugewiesen wird, die in den while-Bedingungen getestet wird:
int k;
while ((k=(int)(Math.random()*10))<9); // leere Anweisung
System.out.println(k); // :: 9
while ((k=(int)(Math.random()*10))<8)
System.out.println(k); // :: Zufallszahlen zwischen 0..7
k=(int)(Math.random()*10);
while (k!=8) {
System.out.println(k); // :: Zufallszahlen ungleich 8
k=(int)(Math.random()*10);
}
Erklärung: Die Methode random() der Klasse Math liefert Zufallszahlen vom Typ double im Intervall [0,1).
Der Ausdruck (int)(Math.random()*10) liefert Zahlen vom Typ int zwischen 0 und 9.
do-Anweisung
Die do-Anweisung ist eine Variante von while und hat folgende Form:
do
statement
while (condition);
do vs. while
Der Unterschied zur while-Schleife liegt darin, dass statement zumindest einmal ausgeführt wird, bevor die Bedingung false werden kann.
Beispiele
Die nachfolgende Schleife wird nur einmal durchlaufen, d.h., sie ist äquivalent zu einem einfachen println().
do
System.out.println(Math.random());
while (false);
Im folgenden Code-Fragment wird zur Ziffer k das zugehörige Zeichen c erzeugt, und Ziffer sowie Zeichen werden an jeweils einen String angehängt (wobei die implizite Konvertierung ausgenutzt wird):
String s1="", s2="";
int k;
char c;
do {
k=(int)(Math.random()*10); // Zahl 0..9
c=(char)(k+'0'); // Ziffer '0'.. '9'
System.out.println(c==k); // :: false
s1+=k; s2+=c; // anhängen
an s1, s2
} while (s1.length()<5);
System.out.println(s1+"="+s2); // ::
87039 = 87039
Erklärung: Beim Vergleich c==k werden zwei Werte vom Typ int verglichen, was natürlich false ergibt.
Bevor k und c an s1 bzw. s2 angehängt werden, erfolgt eine Umwandlung in zwei Strings, die dann wieder gleich sind.
for-Anweisung
Die for-Anweisung hat gegenüber den beiden anderen Schleifen den Vorteil, alle relevanten Informationen zur Iteration an einer Stelle, dem Kopfteil, zusammenzufassen.
Die for-Schleife hat folgende Form:
for
(initialisations; condition; iterations)
statement
Komma-Separator
Dabei sind initialisations und iterations kommaseparierte Listen von Expression-Statements (siehe 3.2.3):
initialisations:
initialisation [ ,initialisation,... ]
iterations: iteration [
,iteration,... ]
Deklaration in for
|
Die Initialisierungsanweisungen können mit genau einer Deklaration beginnen, d.h. nachfolgend sind keine mehr möglich (siehe nächstes Beispiel). Die deklarierten Variablen sind lokal und existieren nur innerhalb der for-Schleife. |
|
Jede der drei Anweisungen Initialisierung, Bedingung oder Iteration kann leer sein. |
![Icon](common/R.gif)
Die Ausführung der for-Anweisung
Die Ausführung der Anweisung läuft wie folgt ab:
1. |
Sofern nicht leer, wird/werden die Initialisierungs-Anweisung(en) ausgeführt. |
2. |
Sofern nicht leer, wird die Bedingung ausgewertet. Ist sie leer oder true, wird die Anweisung statement ausgeführt, ansonsten ist die for-Schleife beendet. |
3. |
Sofern nicht leer, wird/werden die Iteration(en) ausgeführt. Dann wird erneut der zweite Punkt ausgeführt. |
Variationen der
for-Schleife
Die for-Schleife kommt aufgrund ihrer Flexibilität in unzähligen Variationen vor, von denen einige nachfolgend vorgestellt werden sollen.
Endlosschleife:
for (;;) { ... } // endlos aufgrund des 2./3. Punktes
Initialisierung mit Deklaration:
double[] darr= {1.,0.,-.1,-4e-2,5d};
double sum=0.0;
for (int i=0; i<darr.length;i++) // Deklaration von i
sum+=darr[i];
System.out.println(sum); // ::
5.86
System.out.println(i); // C-Fehler
Nach der ersten der genannten Regeln existiert die lokale Variable i nur innerhalb der for-Schleife, ist also danach nicht mehr existent.
Zwei mögliche Alternativen sind:
Leere Iteration:
for (int i=0; i<darr.length;) // Iteration
ist zu suchen
sum+=darr[i++];
Leere Anweisung:
for (int i=0; i<darr.length; sum+=darr[i++]);
System.out.println(i); // wieder C-Fehler
(s. Regel)
Der Iterationsteil erledigt mit einem Seiteneffekt auch gleichzeitig die Summenoperation.
Leere Bedingung:
Siehe dazu das Beispiel im folgenden continue-Abschnitt.
Deklaration, Iteration mit mehreren Variablen:
int[] iarr= {1,2,3,4,5};
for (int i=0, j=iarr.length-1, t; i<j; i++, j--)
{
t= iarr[j]; iarr[j]=iarr[i]; iarr[i]=t;
}
Aufruf einer Methode in einem for-Teil:
for (int i=0; i<iarr.length; System.out.print(iarr[i++]+"
"));
Der Typ darf bei der Deklaration nur einmal aufgeführt werden. Die folgenden zwei Variationen sind also falsch (Fehler sind hervorgehoben!):
for (int i=0, int j=iarr.length-1, t; i<j; i++,j--)
{...}
for (int i=0, j=iarr.length-1, int t; i<j; i++,j--)
{...}
for-Schleife mit Objekten
Iteration mit Objekten:3
Eine for-Schleife kann nicht nur durch eine Menge primitiver Typen wie int iterieren, sondern auch durch eine Menge von Objekten.
Nachfolgend wird eine String-Kollektion4 stringSet angelegt. Anschließend wird mittels der Methoden iterator() ein Iterator-Objekt5 für diese Kollektion angelegt.
Iteration in einer Collection
Mit den Methoden hasNext() und next() des Iterators und einer for-Schleife können alle Elemente von stringSet durchlaufen werden.
Collection stringSet= new HashSet();
stringSet.add("Hallo"); stringSet.add("Welt");
for (Iterator i= stringSet.iterator(); i.hasNext();)
System.out.print(i.next()+" "); // :: Hallo
Welt
Erklärung: In Analogie zum int-Iterator wurde hier bewusst ein i gewählt. Wie zu sehen, ist der Iterationsteil leer, da i.next() einerseits das Objekt – in diesem Fall den String – und andererseits zum nächsten Element in der Kollektion wechselt.
3.3.3 Sprunganweisungen ![down](common/down.gif) ![top](common/top.gif)
Java kennt keine allgemeine Sprunganweisung in Form von goto wie C/C++. In speziellen Fällen erlaubt Java jedoch durchaus kontrollierte Sprünge. Zu den Sprunganweisungen zählen:
Sprunganweisungen:
break & Co.
|
break: gültig innerhalb von switch, Schleifen und mit Marken versehenen Blöcken |
|
continue: gültig innerhalb von Schleifen |
|
return: gültig außerhalb eines Initialisierers |
|
throw: muss eine im Programmkontext gültige Ausnahme werfen6 |
Bei Sprunganweisungen prüft der Compiler, ob der nachfolgende Code noch erreicht werden kann:
|
Code, der aufgrund von Sprunganweisungen nicht mehr ausgeführt werden kann, erzeugt einen Compilerfehler. |
Marke (Label)
Label: Markieren von Anweisungen
Mit Ausnahme von Deklarationen können Anweisungen (inklusive Blöcke) mit einer Marke versehen werden.
Marken müssen gültige Identifier sein und – separiert durch einen Doppelpunkt – vor die zu markierende Anweisung gesetzt werden. Anweisungen mit derselben Marke dürfen nicht ineinander geschachtelt werden. Die Namen für Marken und Variablen können durchaus gleich sein.
Marken sind nur vor Anweisungen bzw. Blöcken sinnvoll, in denen ein break oder continue vorkommt.
continue-Anweisung
Eine continue-Anweisung kann nur in einer Iteration auftreten. Die Form ist:
continue [label];
![Icon](common/R.gif)
Die Ausführung der continue-Anweisung
Die Ausführung läuft wie folgt ab:
1. |
Ohne Marke wird die innerste Iterationsanweisung an dieser Stelle abgebrochen, mit Marke die mit dieser Marke versehene Iteration. |
2. |
Ist die Iteration eine for-Schleife, wird/werden als Nächstes die Iterationsanweisung(en) iterations (siehe for-Definition) ausgeführt. |
3. |
Es wird die Bedingung (condition) der Iterationsanweisung ausgewertet. Ist sie true, wird der nächste Schleifendurchlauf durchgeführt, ansonsten wird die Schleife beendet. |
Beispiel
Im folgenden Code-Fragment werden fünf Summen von zufälligen Zahlen aus dem Intervall [0.5;1) berechnet, bis die Summe größer als 10.0 ist. Die Anzahl der zufälligen Werte und die Summe werden ausgegeben:
double sum, k;
m1: for (int i=0; i<5; i++) {
sum= 0.0;
for (int j=0;;j++) { // leere Bedingung
if ((k=Math.random())<0.5) continue;
sum+=k;
if (sum>10.0) {
System.out.println(j+": "+sum); continue m1;
}
}
}
Die innere continue-Anweisung gehört zur inneren for-Schleife. Um zur äußeren for-Schleife zu springen, muss continue mit einer Marke, hier m1, versehen werden.
break-Anweisung
Eine break-Anweisung hat die folgende Form:
break [label];
Ohne Marke kann break nur in switch-Anweisungen oder Iterationen verwendet werden, mit Marke innerhalb jeder Anweisung, die mit dieser Marke versehen ist.
![Icon](common/R.gif)
Die Ausführung der break- Anweisung
Die Ausführung der break-Anweisung läuft wie folgt ab:
|
Ohne Marke wird die innerste switch-Anweisung bzw. Iteration beendet, in der break enthalten ist. |
|
Mit Marke wird die switch-Anweisung bzw. Iteration beendet, die mit dieser Marke versehen ist (und break enthält). |
Beispiele
Im folgenden Code-Fragment werden so lange zufällige Zahlen zwischen 0..9 erzeugt, bis alle Zahlen im Array iarr getroffen wurden. Treffer werden mit true in einem logischen Array barr markiert.
int[] iarr= {3,0,7};
boolean[] barr= new boolean[iarr.length];
int n=0, k; // n: Anzahl Treffer
m1: while (true) { // label m1
k=(int)(Math.random()*10);
for (int i=0; i<iarr.length; i++)
if (!barr[i] && k==iarr[i]) { // Treffer
barr[i]= true; // markieren
if (++n<3) break; // springt aus for
else break m1; // springt aus while
}
}
Im nächsten Beispiel springt break aus dem zugehörigen Block:
m: {
if (true) break m; // ohne if gibt es einen C-Fehler
System.out.println("unerreichbar");
}
Ohne die if-Anweisung ist der Code in println() unerreichbar, d.h., es gibt einen Compilerfehler. Mit if ist der Compiler guten Glaubens, der Code hinter break könne vielleicht doch noch erreicht werden.
3.3.4 try-Anweisung ![down](common/down.gif) ![top](common/top.gif)
Die try-Anweisung hat folgende Form:
try { tryStatementSequenz } [catchClauses] [finallyClause]
Mindestens einer der optionalen Anweisungsteile catchClauses bzw. finallyClause muss vorhanden sein.
Die try-Anweisung wird immer in Verbindung mit der Behandlung von Ausnahmen (exception-handling mechanism) vorgestellt. Dies ist insofern richtig, da der try-Block mit der catch-Option eine (von zwei) Möglichkeiten ist, Ausnahmen abzufangen.
Die Behandlung von Ausnahmen mittels catchClauses wird später zusammen mit den Ausnahmen behandelt. Im Folgenden wird die weniger bekannte Variante
try { tryStatementSequenz }
finally { finallyStatementSequenz }
besprochen.
![Icon](common/R.gif)
Die Ausführung von try-finally
Die Ausführung läuft wie folgt:
|
Wird (irgend)eine Anweisung des try-Blocks tryStatementSequenz ausgeführt, werden abschließend (nach try und catch) die Anweisungen der finallyStatementSequenz ausgeführt, sofern nicht |
|
|
im try-Block eine Anweisung System.exit() erfolgt oder |
|
|
der ausführende Thread vorher »stirbt«.7 |
|
Eine return-, break- oder continue-Sprunganweisung im try-Block wird erst nach der Ausführung des finally-Blocks durchgeführt. |
|
Wird ein Ergebnis mittels return zurückgeliefert, kann durch den finally-Block der Wert des Ergebnisses allerdings nicht mehr verändert werden. |
![Icon](common/i.gif)
Cleanup-Idiom
Cleanup-Idiom: Die try-finally-Anweisung stellt sicher, dass Aktionen, die abschließend ausgeführt werden müssen, auch unbedingt zur Ausführung kommen.
|
Das Einsatzfeld des Cleanup-Idioms ist größer als erwartet:
|
Code, der Ressourcen freigeben muss, z.B. Dateien schließen oder Netzwerkverbindungen abbauen, und zwar unabhängig davon, wie die vorherigen Aktionen beendet wurden, gehört besser in eine try-finally-Anweisung. |
|
Da Java im Gegensatz zu C++ keine expliziten Destruktoren besitzt, gehören dazu auch alle Cleanup-Anweisungen, die (irrtümlich) in der finalize-Methode für Objekte untergebracht werden (siehe hierzu auch 5.9.1).8 |
Das Idiom wird erst später benutzt.
Beispiele
Die statische Methode trySum() demonstriert die oben angegebene Ausführungsregel:
static double trySum(double[] darr) {
double sum=0.0;
for (int i=0; i<darr.length;sum+=darr[i++]);
try {
return sum;
} finally {
sum=0.0;
Arrays.fill(darr,0.0); // setzt alle Elemente auf 0.0
}
}
Der Aufruf mit einem Array darr liefert dann:
double[] darr= {0,1,2};
System.out.println(trySum(darr)+" "+darr[2]); // :: 3.0 0.0
Eine zu einer for-Schleife äquivalente while-Schleife kann z.B. wegen einer enthaltenen continue-Anweisung nur mittels try-finally realisiert werden.
Der äqivalente Code ist nebeneinander angeordnet:
for (int i= 0; i<10; i++) {
if (i%2== 0)
continue;
System.out.print(i+" ");
}
|
int i=0;
while (i<10) {
try {
if (i%2== 0)
continue;
System.out.print(i+" ");
} finally { i++; }
}
|
3.4 Deklaration ![down](common/down.gif) ![top](common/top.gif)
Ohne Deklaration ist es unmöglich, ein Java-Programm zu schreiben. Deklariert werden
|
Klassen und Interfaces, |
|
Variablen (lokale und Member- bzw. Feld-Variablen) sowie |
|
Methoden und Konstruktoren von Klassen. |
Modifier: Modifikationen von Klassen bzw.
Klassenmitgliedern
Alle Deklarationen erlauben optionale Modifikationen mittels eines oder mehrerer Modifikatoren (Modifiers).
Die erlaubten Modifikatoren sind abhängig von der Art der Deklaration und werden zur Unterscheidung mit einem Präfix versehen, z.B. bezeichnet cModifiers die erlaubten Modifikatoren für Klassen.
Da einige Modifikatoren erst im Zusammenhang mit Vererbung bzw. Threads verstanden werden können, werden nur die zu diesem Kapitel passenden besprochen.
3.4.1 Klassen und Interfaces ![down](common/down.gif) ![top](common/top.gif)
Klassen sowie Interfaces sind Kompilations-Einheiten (siehe erstes Kapitel) und werden wie folgt deklariert:
[cModifiers] class ClassName [SuperClassInterfaces]
{...}
[public] interface InterfaceName
[SuperInterfaces] {...}
Erlaubte cModifier für Klassen sind public, abstract und final.
public: Erlaubt den Zugriff auf Klassen bzw. Interfaces auch außerhalb des Packages (siehe 1.2.3).
abstract: Erlaubt keine Anlage von Instanzen der Klasse. Dies ermöglicht es, die Klasse nicht vollständig zu implementieren.
final: »Darf nicht mehr geändert werden«
final: Bedeutet so viel wie »darf nicht mehr geändert werden« und verhindert bei Klassen die Anlage von Sub- bzw. Unterklassen.
Default-Zugriff: nur im Package im Zugriff
Die Modifikatoren abstract und final werden noch einmal in Kapitel 5, Vererbung, behandelt.
default (friendly): Wird vor class bzw. interface kein Modifikator verwendet, ist dies die Default-Zugriffsart, die häufig auch friendly oder Package-Access genannt wird.
Klassen bzw. Interfaces stehen dann nur innerhalb des Packages im Zugriff, außerhalb des Packages können sie nicht benutzt werden.
Ein einfaches Beispiel einer Klassen-Deklaration, die nur im Package im Zugriff steht:
class Color {
int rgb; // 8 Bit pro: alpha, red, green, blue
}
Interface
Bei einem Interface kann zwar ohne Fehler auch der Modifikator abstract verwendet werden, sinnvoll ist aber nur der Modifikator public, denn:
Interfaces sind immer abstrakt
|
Da Interfaces implizit abstrakt sind (und dies logisch auch nur sein können), ist der Modifikator abstract vor einem Interface überflüssig und zu vermeiden.9 |
3.4.2 Variable ![down](common/down.gif) ![top](common/top.gif)
![Icon](common/R.gif)
Streng typisiert: Keine Variable ohne Typ
Java ist eine streng typisierte Sprache. Dies bedeutet:
|
Jede Variable muss vor ihrer Benutzung deklariert und einen Wert zugewiesen bekommen. |
|
Jeder Variablen wird bei der Deklaration ein primitiver Typ oder Objekttyp zugewiesen. |
Variablen können bei der Deklaration explizit oder implizit mit Hilfe von Literalen, Variablen oder Methoden initialisiert werden (siehe 1.6).
Die Deklaration von Variablen hat die Form:
[vModifiers] Type varName [= initialValue]
[, ...] ;
Die Option [,...] bedeutet, dass mehrere Variablen – durch Komma getrennt – vom gleichen Typ deklariert und initialisiert werden können.
Erlaubte vModifiers für Instanz- und Klassen-Variablen sind:
public, protected,
private, static,
final, transient,
volatile
Lokale Variablen erlauben nur den Modifikator final.10
final
![Icon](common/R.gif)
Die Wirkung von final
Die Bedeutung »darf nicht mehr geändert werden« verhindert bei Variablen, dass ein einmal gesetzter Wert nicht mehr geändert werden kann. Die Variable ist eine Konstante. Für die Wirkung von final gilt folgende Regel:
|
Bei Variablen vom primitiven Typ ist der Wert der Variablen konstant. |
|
Bei Referenz-Variablen ist (nur) die Referenz konstant, der Wert des Objekts, das referenziert wird, kann jedoch geändert werden. |
blank final
Gegenüber C/C++ bietet Java die Möglichkeit, den Wert nicht direkt bei der Deklaration der final-Variable zu setzen, sondern erst später. Diese Art nennt man blank final.
Bei Klassen- und Instanz-Variablen muss die Initialisierung der blank finals allerdings sofort beim Laden der Klasse bzw. der Objektanlage erfolgen.11
Beispiele
Die folgenden Anweisungen demonstrieren die Auswirkung von final auf Referenzen:
final float E=2.7182818f; // der Wert von E ist
konstant
final int[] iarr= {-1,2,3}; // Referenz konstant
int[] jarr= {1,2,3};
iarr[0]= 1; // ok, da Änderung am Wert des Objekts
jarr= iarr; // auch ok, jarr ist nicht konstant
iarr= jarr; // C-Fehler: Referenz soll geändert werden
Die Methode finalTest() initialisiert eine lokale blank final Variable E erst am Ende:
void finalTest () {
final float E; // hier noch kein Wert!
//... hier Code ohne Zugriff auf E
E= 2.7182818f; // nun Wert nicht mehr änderbar
}
Lokale Variable
Java-Programme sind hinsichtlich Variablen recht einfach aufgebaut:
Packages enthalten Klassen, Klassen enthalten Felder und Methoden, Methoden und Blöcke enthalten lokale Variablen.
Lokale Variable: Lebensdauer und Gültigkeit
Lokale Variablen haben nur eine beschränkte Lebenszeit. Sie werden bei Eintritt in die Methode oder den Block, in dem sie deklariert sind, erschaffen und bei Austritt zerstört bzw. entsorgt.
Damit stehen lokale Variablen nur innerhalb ihres Gültigkeitsbereichs im Zugriff.
Lokale Variablen müssen immer explizit initialisiert werden (siehe 1.6), wobei dies der Compiler »rigoros« prüft, wie anhand der nachfolgenden Methode oops() zu erkennen ist.
Beispiele
float oops(float p) {
float res;
if (p!=0) res=1/p;
return res; // C-Fehler
}
Die bereits ungültige lokale Variable j erzeugt auch in der nächsten Methode uups() einen Compiler-Fehler:
void uups () {
{ int j=2; } // j nur im Block gültig
System.out.println(j); // C-Fehler
}
Klassen- und Instanz-Variable
Alle Felder von Klassen werden grundsätzlich initialisiert (siehe 1.6). Man unterscheidet bei Feldern
|
Klassen-Variablen, die nur eimal pro Klasse vorkommen und |
|
Instanz-Variablen, die in jeder Instanz der Klasse existieren. |
Klassen-Variablen existieren nach Laden der Klasse
Klassen-Variablen werden durch den Modifikator static gekennzeichnet, weshalb sie auch statische Variablen genannt werden. Sie werden – bevor irgendwelche Instanzen existieren – bereits beim Laden der Klasse angelegt und initialisiert.
Instanz-Variablen existieren nach Erschaffen des Objekts
Instanz-Variablen werden erst mit der Erzeugung der Instanz einer Klasse (z.B. mittels new) angelegt und initialisiert.
Beispiel
Die Klasse Color (aus 3.4.1) wird um eine konstante Klassen-Variable erweitert, die zum Ausmaskieren der Farbe Rot benutzt werden kann:
class Color {
final static int REDMASK= 0xff0000; // Klassen-Variable
int rgb; // Instanz-Variable
}
In anderen Klassen kann man direkt auf die Klassen-Variable REDMASK von Color zugreifen, ohne ein Color-Objekt erschaffen zu müssen:
class TestColor {
static void test() {
System.out.println(Color.REDMASK); // über Klasse
Color c= new Color();
System.out.println(c.rgb); // nur über Objekt
System.out.println(new Color().rgb); // auch ok!
}
}
In der letzten Anweisung wird über den Referenzausdruck (siehe 3.5.3) new Color() und nicht über eine Referenz wie c direkt auf die Instanz-Variable rgb zugegriffen.
Initialisierung eines Arrays
Array-Initializer:
Anlage kleiner Arrays ohne new
Bei der Array-Deklaration kann auch ohne new mit Hilfe eines Array-Initializers ein Array-Objekt erschaffen werden, auf das die Referenz dann zeigt. Die Initialisierung von Arrays bei der Anlage ist besonders für kleine Arrays sehr praktisch.
Array-Initializer ist kein Array-Literal
int[][] imatrix= {{1,0},{0,1}}; // 2x2 Einheitsmatrix
Diese spezielle Form kann aber nur bei der Deklaration direkt verwendet werden, anschließend nicht mehr.
Beispiel
int[][] imatrix;
imatrix= {{1,0,0},{0,1,0},{0,0,1}}; // C-Fehler
imatrix= new int[][] {{1,0,0},{0,1,0},{0,0,1}}; // ok!
3.4.3 Methode ![down](common/down.gif) ![top](common/top.gif)
Methoden werden mit folgender Syntax deklariert:
[mModifiers] ResultType methodName (parameterList)
[ThrowsList]
{ /* Block */ }
Erlaubte mModifier sind:
public, protected, private, static, final, abstract,
native,
synchronized
Der ResultType steht für
|
den Typ des Werts, den die Methode bei Aufruf mittels des Schlüsselworts return zurückliefert. Der Wert ist dabei das Ergebnis eines typkompatiblen Ausdrucks, der hinter return steht. |
|
void, d.h., die Methode liefert keinen Wert als Ergebnis zurück und return darf nur ohne einen Ausdruck verwendet werden. |
void ist kein Typ
In Java ist void kein Typ, d.h., eine Typumwandlung mittels eines Casts nach void wie in C/C++ ist nicht möglich:
(void)
anyExpression; // C-Fehler
Das Resultat einer Methode hat einen beliebigen Typ
Für das Resultat einer Methode gibt es keine Einschränkung wie bei C/ C++. Alle Typen – auch mehrdimensionale Arrays – sind erlaubt.12
Die parameterList ist eine kommaseparierte Liste von Deklarationen einzelner Parameter-Variablen. Hat die Methode keine Parameter, so muss trotzdem ein leeres Klammernpaar stehen.
final-Parameter: nicht veränderbar in der Methode
Der einzige erlaubte Modifikator bei der Deklaration der Parameter ist final. Einem final-Parameter kann in der Methode kein Wert zugewiesen werden.
Eine Initialisierung der Parameter mit einem Default-Wert wie bei C/C++ ist nicht erlaubt.
throws:
Auflistung aller nicht abgefangenen, zu prüfenden Ausnahmen
Die ThrowsList gibt alle zu prüfenden Ausnahmen (checked Exceptions) an, die von der Methode während der Laufzeit ausgelöst, aber nicht behandelt werden.
Ausnahmen sind Klassen, die von der Klasse java.lang.Throwable abgeleitet sind. Sie werden hinter dem Schlüsselwort throws durch Komma getrennt aufgeführt (siehe 7.4.2).
Beispiele
Die Klasse Color wird um eine Methode getRed() mit Rückgabewert und setRed() ohne Rückgabewert erweitert, d.h., return muss entweder mit oder ohne Wert aufgeführt werden:
class Color {
final static int REDMASK= 0xff0000;
int rgb; // 8 Bit pro: alpha, red, green, blue
int getRed() {
return (rgb & REDMASK) >>> 16;
}
void setRed(int red) {
if (red<0 || red>0xff) return; // nur 0..255 erlaubt
rgb=(rgb&= 0xff00ffff)|(red<<16);
}
}
Die statisch erklärte Methode createIntMatrix() legt eine Integer-Matrix mit rows-Zeilen und cols-Spalten an und liefert sie als Ergebnis zurück:
public static int[][] createIntMatrix(int
rows, int cols) {
return new int[rows][cols];
}
Signatur
Unter der Signatur einer Methode versteht man den Methodennamen und die Liste der Typen ihrer Parameter, wobei die Anordnung wichtig ist.
Die Signatur der Methode createIntMatrix() ist
createIntMatrix, int , int
![Icon](common/R.gif)
Zur Signatur zählt nach Java-Konvention nicht der Rückgabetyp.13 Es gilt folgende Regel:
Signaturen: Unterscheidungsmerkmal für Methoden
|
In einer Klasse können keine zwei Methoden mit derselben Signatur vorkommen.14 |
Die Klasse SignaturFehler führt folglich zu einem Compiler-Fehler:
class SignaturFehler {
int foo(int i) {}; // Signatur: foo, int
void foo(int k) {}; // Signatur: foo, int
}
3.5 Aufruf einer Methode ![down](common/down.gif) ![top](common/top.gif)
Der Aufruf bzw. die Ausführung einer Methode ist davon abhängig, ob sie innerhalb oder außerhalb der Klasse aufgerufen wird, in der sie deklariert ist, und ob sie eine Instanz-Methode oder Klassen-Methode ist.
Für alle Methoden ist allerdings der folgende Mechanismus der Werteübergabe der Argumente und des Aufrufs gleich.
3.5.1 Argumente und Wertübergabe ![down](common/down.gif) ![top](common/top.gif)
Parameter,
Argumente
Beim Aufruf einer Methode werden die formalen Parameter durch aktuelle Argumente ersetzt, mit denen die Methode dann ausgeführt wird. Ist die Methode parameterlos, muss – wie bei der Deklaration – hinter dem Methodennamen eine leere Klammer gesetzt werden.
![Icon](common/R.gif)
Aufruf einer Methode
Es sind folgende allgemeinen Regeln zu beachten:
1. |
Für jeden formalen Parameter muss beim Aufruf ein Ausdruck übergeben werden, dessen Typ implizit in den des formalen Parameters umgewandelt werden kann. |
2. |
Bevor die Methode ausgeführt wird, werden erst alle Werte der Argumente von rechts nach links berechnet. |
3. |
Die Werte der Argumentausdrücke sind dann die Werte, die für die formalen Parameter verwendet werden. |
Pass by Value: Wertübergabe
der Argumente per Kopie
Der letzte Punkt wird auch Pass by Value genannt und bedeutet, dass nicht mit dem Argument selbst, sondern mit einer Wertekopie des Arguments gearbeitet wird.15 5
|
Beim Methoden-Aufruf kann statt einer Variablen immer ein Ausdruck als Argument übergeben werden. |
Beispiel
Der Aufruf der Methode createIntMatrix() verdeutlicht die Regeln:
char c= '2';
int[][] imatrix= createIntMatrix(c-='0',++c);
System.out.print(imatrix.length+",");
System.out.print(imatrix[0].length); // ::
2,3
Erklärung: Beide Argumentausdrücke sind vom Typ char. Sie werden nach der zweiten Regel zuerst berechnet. Beide Ergebnisse werden nach der ersten Regel von char nach int umgewandelt und als Wert übergeben.
In den folgenden beiden Abschnitten wird die unterschiedliche Wirkung des Pass by Value auf primitive Typen und Referenz-Typen behandelt.
Argumente vom primitiven Typ
Anhand der Klasse MethodTest1 und ihrer statischen Methode primitiveArgs() soll die Wirkung des Pass by Value anhand von Parametern des primitiven Typs demonstriert werden:
class MethodTest1 {
Test auf NaN
static long primitiveArgs (double d, int i) {
if (d!=d) // wenn NaN Û
Double.isNaN(d)
return i*i; // numeric promotion
else {
d*=d; // ändert den Parameterwert
return (long)d; // Cast notwendig
}
}
// ...
}
Beim letzten return muss der Wert von d explizit nach long konvertiert werden.
Diese Methode wird nun in einem Code-Fragment (innerhalb derselben Klasse) aufgerufen:
Primitive Argumente ändern ihre Werte nie durch Methoden-Aufruf
System.out.println(primitiveArgs(Double.NaN,1));
// :: 1
double x=2.;
System.out.println(primitiveArgs(x,(int)x));
// :: 4
System.out.println(x); //
:: 2.0
Aufgrund Pass by Value hat der Wert von x sich nicht geändert!
Referenz-Argumente
Pass by Value für Referenzen
Da Referenzen nur den Zeiger auf ein Objekt halten, wird bei der Wertübergabe nicht das Objekt kopiert, sondern nur der Zeiger.
![Icon](common/R.gif)
Innerhalb der Methode wird also mit einer zweiten Referenz, einer Kopie des übergebenen Referenz-Arguments, gearbeitet, das auf dasselbe Original-Objekt zeigt.
Es gilt die folgende Regel:
Referenz bleibt unverändert!
|
Ändert man den Referenzwert innerhalb der Methode, ändert dies nicht den Referenzwert des Arguments außerhalb der Methode. |
|
Ändert man das Objekt, auf das die Referenz verweist, innerhalb der Methode, ist die Änderung auch außerhalb der Methode wirksam. |
|
Referenzierte Objekte können verändert werden!
Auch in diesem Fall soll anhand einer Klasse MethodTest2 und ihrer statischen Methode refArgs() die Wirkung des Pass by Value anhand von Referenz-Parametern demonstriert werden:
class MethodTest2 {
static int[] refArgs (int[] v1,
int[] v2) {
int[] res= v2; // Anlage eines Resultat-Vektors?
for (int i=0; i<res.length;i++)
res[i]= v1[i]+v2[i]; // Summe der Vektoren
v1= v2;
return res;
}
}
Die Methode wird in einer anderen Klasse wie folgt benutzt:
int[] iv1= {1,0,0}, iv2= {0,1,-1};
int[] iv3= MethodTest2.refArgs(iv1,iv2);
System.out.println(iv1[0]+","+iv1[1]+","+iv1[2]);
// :: 1,0,0
System.out.println(iv2[0]+","+iv2[1]+","+iv2[2]); // ::
1,1,-1
System.out.println(iv3[0]+","+iv3[1]+","+iv3[2]); // ::
1,1,-1
Erklärung: In der Methode refArgs() wird eine lokale Referenz res angelegt, die auf dasselbe Array wie die Referenz v2 zeigt. Damit werden die Ergebnisse der Addition der Vektor-Elemente in v2 gespeichert.
Anschließend wird die Referenz v1 auf den Wert von v2 gesetzt, d.h., innerhalb der Methode zeigt nun auch v1 auf den Ergebnisvektor v2.
Nach der o.a. Regel hat dies dann folgende Auswirkungen nach außen:
|
Die Referenzen iv2 und iv3 zeigen auf dasselbe Array, d.h., die int-Elemente wurden durch den Aufruf geändert (zweite Regel). |
|
Die Referenz iv1 zeigt weiterhin auf {1,0,0}, da v1 nur eine Referenzkopie von iv1 ist (erste Regel). |
Die Anlage einer weiteren Referenz in refArgs() war ziemlich überflüssig, da:
res[i]= v1[i]+v2[i]; Û
v2[i]= v1[i]+v2[i];
Die Anlage eines neuen Arrays wäre wohl besser gewesen:
int[] res= new int[v2.length];
// anstatt: int[] res= v2;
3.5.2 Einfacher Methoden-Aufruf ![down](common/down.gif) ![top](common/top.gif)
Wird eine Methode über ihren einfachen Namen aufgerufen
methodName(actualArguments)
so muss die Methode zur Deklaration der Klasse gehören, in der sie aufgerufen wird. Die Methode kann dabei durchaus zu einer Superklasse oder einem Interface gehören, das die Klasse implementiert (siehe Abschnitt zur Vererbung in Kapitel 5.
3.5.3 Qualifizierender Methoden-Aufruf ![down](common/down.gif) ![top](common/top.gif)
Um auch auf Methoden anderer Klassen zugreifen zu können, wird vor den Methodennamen ein qualifizierender Ausdruck gestellt. Dabei unterscheidet man zwischen Instanz- und Klassen-Methoden.
Methoden-Aufruf über Referenz-Expression auf Instanzen
Instanz-Methoden
Instanz-Methoden operieren auf Instanzen und werden in der Form
referenzExpression.methodName(actualArguments)
aufgerufen. Dabei steht referenzExpression für alles, was eine Referenz auf eine Instanz der entsprechenden Klasse oder des Interfaces liefert, von der oder dem man die Methode aufrufen möchte:
1. |
eine Variable, Array-Element bzw. Literal der Klasse |
2. |
ein Methoden-Aufruf/Operation (mit Referenz als Rückgabewert) |
3. |
einen new-Ausdruck der Klasse |
this und super
4. |
das Schlüsselwort this oder super |
Die letzten beiden Schlüsselwörter sind dazu da, Zweideutigkeiten aufzulösen, d.h., this ist eine Referenz auf das Objekt, das die Methode ausführt, und super ist eine Anweisung, die nachstehende Methode aus einer der Superklassen zu nehmen.16
Beispiele
Nachfolgend werden Methoden über Referenz-Ausdrücke aufgerufen.
i= ("abc"+"de").length());
// siehe 1./2. Punkt oben
Erklärung: Die Operation + liefert eine Referenz auf ein String-Objekt "abcde", d.h., sie ist gleichbedeutend mit "abcde".length(). Zu Klassen-Literalen können nach dem ersten Punkt Instanz-Methoden aufgerufen werden.
j= "abc".substring(1).length(); // siehe 2. Punkt
oben
Erklärung: Die Methode "abc".substring(1) liefert einen (Teil-) String zurück, von dem wieder eine Methode aufgerufen werden kann.
new für Array-Anlage
new für Objekt-Elemente eines Arrays
Color[] c= new Color[1]; // legt ein Array
an
(c[0]= new Color()).setRed(0xab); // siehe 1. Punkt oben
// c[0]= new Color(); c[0].setRed(0xab);
Erklärung: Bei der Anlage eines Arrays von Referenzen auf Objekte werden mit dem new des Arrays nicht die Objekte erschaffen, sondern nur die Referenzen.
Sollen zu den Referenzen auch Objekte erschaffen werden, so sind diese wieder mittels new zu bilden.
Deshalb wird zuerst mittels new eine neue Color-Instanz geschaffen und zugewiesen. Von c[0] wird dann die Methode setRed() aufgerufen (die äquivalente Anweisung befindet sich oben in der letzten Kommentarzeile).
k= new Color().getRed(); // siehe o.a.
3. Punkt
Anonyme Instanzen
Erklärung: Mit new wird eine anonyme Instanz von Color erschaffen, d.h., ihr ist explizit keine Referenz zugewiesen. Über die anonyme Color-Instanz wird dann getRed() aufgerufen. Diese Class Instance Creation Expression ist (laut Tabelle 2.2) im Rang höher als der .Dot-Operator, braucht also nicht geklammert zu werden.
Auf diese Color-Instanz kann anschließend nicht mehr zugegriffen werden.
Klassen-Methoden
Der Aufruf von Klassen-Methoden erfolgt über den Klassennamen:
Einschränkungen für Klassen-Methoden
className.methodName(actualArguments)
Statische bzw. Klassen-Methoden können nur auf statische bzw. Klassen-Variablen (siehe 1.6) zugreifen oder auf Objekte, die ihnen als Argumente übergeben werden. Es gilt folgende Regel:
![Icon](common/R.gif)
Statische Methoden
|
können nicht auf die Instanz-Variablen der Klasse zugreifen, da diese nur in den Objekten der Klassen existieren. |
|
können keine Instanz-Methoden der Klasse aufrufen. |
|
können aufgerufen werden, ohne dass irgendwelche Instanzen der Klasse erschaffen werden müssen. |
Klassen-Methoden:
Eine (neue) Art der prozeduralen Programmierung
Somit können statische Methoden auf Instanzen und ihren Feldern nur operieren, wenn ihnen diese als Argumente übergeben werden.
Sofern statische Methoden nur auf den Parametern/Argumenten operieren, sind sie äquivalent zu Funktionen in prozeduralen Sprachen wie C.
Als Beispiel soll eine swapObject()-Methode dienen, die beliebige Objekt-Referenzen tauscht:
class SwapDemo {
static void swapObject (Object[] o,int first,int second) {
Object t= o[first];
o[first]=o[second];
o[second]= t;
}
}
Hier wird ausgenutzt, dass Änderungen an Array-Elementen durchaus den Methoden-Aufruf überleben. Der Aufruf in einer anderen Klasse erfolgt über den Klassennamen:
Integer iarr[]= {new Integer(1), new Integer(-1)};
Swap.swapObject(iarr,0,1);
System.out.println(iarr[0]+","+iarr[1]); // ::
-1,1
![Icon](common/T.gif)
Statische Methoden können zwar auch über Instanzen aufgerufen werden. Dies ist aber irreführend und sollte unbedingt vermieden werden.
|
3.6 Zusammenfassung ![down](common/down.gif) ![top](common/top.gif)
Dieses Kapitel behandelte Anweisungen, wobei nur die Anweisungen bzw. Optionen besprochen wurden, die nicht den Rahmen eines Kapitels gesprengt hätten.
Neben den üblichen Kontrollanweisungen wurde die try-Anweisung in der try-finally-Variante vorgestellt, die zum ersten Cleanup-Idiom geführt hat.
Einen weiteren Schwerpunkt bildeten die Deklarationen von Klassen, ihrer Modifikatoren, der lokalen Variablen und Felder, der Methoden und der Bedeutung von static und final.
Die Signatur einer Methode wurde angesprochen und ihr Aufruf mit Argumenten. Besonders der Übergabemechanismus Pass by Value für Argumente wurde in Hinblick auf die Auswirkungen für primitive Parameter und Referenz-Parameter anhand von Beispielen vorgestellt.
3.7 Testfragen ![top](common/15x15leer.gif) ![top](common/top.gif)
Zu jeder Frage können jeweils ein oder mehrere Antworten bzw. Aussagen richtig sein.
for (int i=1, j=10; i<j; i++, --j) {
if (j%i==0) {
System.out.println("continue"); continue;
}
if (i==j-1) {
System.out.println("\nbreak"); break;
}
System.out.print(i+":"+j+" ");
}
A: Die erste Zeile der Ausgabe ist: continue
B: Die letzte Zeile der Ausgabe ist: break
C: Ein Teil der Ausgabe ist: 5:6
D: Ein Teil der Ausgabe ist: 3:8
E: Der Code führt zu einem Fehler beim Kompilieren.
int i=0; double d=10.;
A: while (i<d && i<<1) { i++; }
B: while (i<d && i<<1<d) { i++; }
C: while (i++<d--) i++;
D: do i++; d--; while(i<d);
E: do { int i=1; i++; } while (i<d);
F: for (i=1,int j=10; i<j; i++, j--);
i: for (int i= 0; i<3; i++)
for (int j=3; j>0; j--) {
if (i==j) { System.out.print("b "); break i; }
else if (j>i) { System.out.print("c "); continue; }
System.out.print(i+":"+j+" ");
}
A: Ein Teil der Ausgabe ist: 0:1
B: Ein Teil der Ausgabe ist: 1:2
C: Ein Teil der Ausgabe ist: b
D: Ein Teil der Ausgabe ist: c
E: Der Code führt aufgrund der Marke zu einem Compiler-Fehler.
m: for (int i= 4; i<7; i++)
switch (i%4) {
case 0: System.out.println("1.case"); break;
case 3-1: System.out.println("2.case"); break m;
case 3: System.out.println("3.case");
default: System.out.println("default");
}
A: Ein Teil der Ausgabe ist: 1.case
B: Ein Teil der Ausgabe ist: 2.case
C: Ein Teil der Ausgabe ist: 3.case
D: Ein Teil der Ausgabe ist: default
E: Der Code führt zu einem Compiler-Fehler.
for (int i=0; i<5; i++) {
m: try {
if (i==3) {
System.out.println("break"); break;
}
try {
if (i%2==0) i++;
} finally {
i++; break m;
}
} finally {
System.out.println("finally"); i++;
}
System.out.println(i+" ");
}
A: Ein Teil der Ausgabe ist: break
B: Ein Teil der Ausgabe ist: 3
C: Ein Teil der Ausgabe ist: 7
D: Ein Teil der Ausgabe ist: finally
static void f6(int[] iarr, int i, boolean b) { /*...*/
}
Die nachfolgenden Argumente iarr und barr sind wie folgt deklariert:
int[] iarr= {1,2,3};
byte[] barr= {1,2,3};
Welche Aufrufe sind richtig?
A: f6(null,'c',!true);
B: f6({1,2,3},1,true);
C: f6(new int [] {1,2,3},2<<1,0./0.==0./0.);
D: f6(iarr,1.0,false);
E: f6(barr,1,true);
class F7 {
static int i;
public static void main (String a[]) {
final int j;
System.out.println(i);
System.out.println(j=10);
}
}
A: F7 ist keine Applikation, da main() fehlerhaft ist.
B: F7 ist keine Applikation, da F7 nicht public erkärt ist.
C: F7.main() kann nicht außerhalb des Packages aufgerufen werden.
D: Innerhalb des Packages ist der Aufruf F7.main("Test"); korrekt.
E: final int j; führt zu einem Compiler-Fehler, da die Initialisierung fehlt.
class F8 {
// 1
final int a=1; // 2
static final int b=2; // 3
public static void f (double a, final int c) { // 4
int b=3; // 5
System.out.println(a); // 6
System.out.println(b); // 7
System.out.println(c=7); // 8
System.out.println(1/0); // 9
}
}
A: Fehler in // 4: Der Name a ist bereits vergeben.
B: Fehler in // 5: Der Name b ist bereits vergeben.
C: Fehler in // 6: Doppeldeutig, da unklar, welches a gemeint ist.
D: Fehler in // 7: Doppeldeutig, da unklar, welches b gemeint ist.
E: Fehler in // 8: c kann kein Wert zugewiesen werden.
F: Fehler in // 9: Integer-Division durch Null.
class F9 {
void f9() { return; }
}
A: Der Code von f9() ist fehlerhaft.
B: Der Aufruf von F9.f9(); macht überhaupt nichts, d.h. ändert nichts.
C: f9() liefert keinen Wert.
D: Die Signatur von f9() besteht aus void und dem Namen f9.
A: Der einzige erlaubte Modifikator für Parameter ist final.
B: Eine Methode kann beliebig viele return-Anweisungen haben.
C: Eine lokale Variable kann static final erklärt werden.
D: Alle Methoden einer Klasse können sich gegenseitig aufrufen.
E: Eine Klasse, die final erklärt wird, kann nur final erklärte Methoden enthalten.
F: Wird eine Methode final erklärt, muss die zugehörige Klasse auch final erklärt werden.
G: Eine Klasse (die nicht in einer anderen enthalten ist) kann nicht private erklärt werden.
H: Eine (nicht final erklärte) Klassen-Variable kann nur von static erklärten Methoden geändert werden.
I: Die Methode private static void main(String[] args) erzeugt einen Compiler-Fehler, da sie private erklärt wurde.
J: Eine public erklärte Methode kann auch außerhalb des Packages aufgerufen werden.
K: Wird einer Methode mit einem Array-Parameter beim Aufruf ein Array übergeben, müssen die Elemente den gleichen Typ haben oder implizit in den Typ der Elemente des Array-Parameters konvertiert werden können.
L: Wird einer Methode mit einem Referenz-Parameter beim Aufruf eine Referenz mit einem anderen Typ übergeben, muss diese implizit in den Typ des Parameters konvertiert werden können.
1 Detailänderungen mit dem Ziel der Verbesserung, wie z.B. Gültigkeitsbereich von Iterationsvariablen bei for-Schleifen oder um C/C++ Tricks zu unterbinden (wie z.B. Duff´s Device).
2 Konstante Ausdrücke kann bereits der Compiler errechnen.
Siehe hierzu den final-Modifier.
3 Zum Verständnis des Kapitels ist dieses Beispiel nicht notwendig. Es setzt Kenntnisse zum Package java.util voraus, welches erst im zweiten Teil des Buchs besprochen wird.
4 Kollektionen sind »Behälter« für beliebige Objekte, in diesem Fall eine (mathematische) Menge stringSet von Strings (siehe 14. Kapitel).
5 Mit Hilfe von Iteratoren können die Elemente einer Kollektion »besucht« werden.
6 Wird in Kapitel 7, Ausnahmen, behandelt.
7 Siehe hierzu Kapitel 9, Threads. Es reicht allerdings auch,
den Computer auszuschalten.
8 Siehe hierzu auch den Abschnitt 5.9.1
9 Dies steht leider im Gegensatz zu vielen Interface-Deklarationen in der Plattform.
10 Interfaces und die vModifiers (bis auf static und final) werden erst im Zusammenhang mit Vererbung (siehe Kapitel 5) näher besprochen.
11 Siehe zu Konstruktoren 5.5.2 und statischen Initialisierern 5.7.2
12 Damit dies syntaktisch möglich ist, werden die Array-Klammern direkt hinter den Typ geschrieben und nicht wie bei C/C++ hinter die Variable.
13 Vorsicht: Die Definition ist nicht einheitlich. Beim Design anderer Sprachen zählt in der Regel auch der Rückgabetyp zur Signatur.
14 Vorsicht: Konstruktoren zählen nicht zu den Methoden (siehe auch 5.6.2).
15 Ist das Argument ein Ausdruck, ist evident, dass mit dem Ergebnis
gearbeitet werden muss.
16 super wird erst im Zusammenhang mit Vererbung (siehe Kapitel 5) behandelt.
|