Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java 2 von Friedrich Esser
Designmuster und Zertifizierungswissen
Zum Katalog
gp Kapitel 3 Anweisungen
  gp 3.1 Überblick über Anweisungen
  gp 3.2 Grundlegende Anweisungen
  gp 3.3 Kontrollanweisungen
  gp 3.4 Deklaration
  gp 3.5 Aufruf einer Methode
  gp 3.6 Zusammenfassung
  gp 3.7 Testfragen

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.


Galileo Computing

3.1 Überblick über Anweisungen  downtop

Zu der Gruppe der Anweisungen zählen insbesondere:

gp  Deklarationen und Objekt-Anlage
gp  Ausdrücke und Zuweisungen (siehe auch Kapitel 2)
gp  Kontrollanweisungen, die den Programmablauf (Flow Control) beeinflussen
gp  Aufruf von Methoden
gp  Behandlung von Ausnahmen (Exceptions)
gp  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

3.2 Grundlegende Anweisungen  downtop

Nachfolgend werden einfache Arten von Anweisungen vorgestellt, die Bestandteil selbst einfacher Programme sind.


Galileo Computing

3.2.1 Leere Anweisung  downtop

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.


Galileo Computing

3.2.2 Block (Compound-Statement)  downtop

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.


Galileo Computing

3.2.3 Ausdruck als Anweisung (Expression-Statementdowntop

Folgende Ausdrücke bewirken neben der Operation einen Seiteneffekt:

gp  Zuweisung
gp  Inkrementieren, Dekrementieren
gp  Methoden-Aufruf (siehe 3.5)
gp  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

Galileo Computing

3.3 Kontrollanweisungen  downtop

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.

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.


Galileo Computing

3.3.1 Selektion  downtop

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

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
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
Abbildung 3.2   switch-Anweisung

Mit statementSequenz ist kein Block, sondern sind null bis beliebig viele Anweisungen gemeint.

Unter constExpression versteht man konstante Ausdrücke mit unterschiedlichen Werten, die sich implizit in den Typ int umwandeln lassen. Die break-Anweisung bzw. der default-Teil sind optional.

Icon

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.


Galileo Computing

3.3.2 Iterationen  downtop

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
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

gp  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.
gp  Jede der drei Anweisungen Initialisierung, Bedingung oder Iteration kann leer sein.

Icon
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:

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-Kollektion stringSet angelegt. Anschließend wird mittels der Methoden iterator() ein Iterator-Objekt 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.


Galileo Computing

3.3.3 Sprunganweisungen  downtop

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.

gp  break: gültig innerhalb von switch, Schleifen und mit Marken versehenen Blöcken
gp  continue: gültig innerhalb von Schleifen
gp  return: gültig außerhalb eines Initialisierers
gp  throw: muss eine im Programmkontext gültige Ausnahme werfen

Bei Sprunganweisungen prüft der Compiler, ob der nachfolgende Code noch erreicht werden kann:

gp  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
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
Die Ausführung der break- Anweisung

Die Ausführung der break-Anweisung läuft wie folgt ab:

gp  Ohne Marke wird die innerste switch-Anweisung bzw. Iteration beendet, in der break enthalten ist.
gp  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.


Galileo Computing

3.3.4 try-Anweisung  downtop

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
Die Ausführung von try-finally

Die Ausführung läuft wie folgt:

gp  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
    gp  im try-Block eine Anweisung System.exit() erfolgt oder
    gp  der ausführende Thread vorher »stirbt«.
gp  Eine return-, break- oder continue-Sprunganweisung im try-Block wird erst nach der Ausführung des finally-Blocks durchgeführt.
gp  Wird ein Ergebnis mittels return zurückgeliefert, kann durch den finally-Block der Wert des Ergebnisses allerdings nicht mehr verändert werden.

Icon
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:

gp  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.
gp  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).

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++; }
}


Galileo Computing

3.4 Deklaration  downtop

Ohne Deklaration ist es unmöglich, ein Java-Programm zu schreiben. Deklariert werden

gp  Klassen und Interfaces,
gp  Variablen (lokale und Member- bzw. Feld-Variablen) sowie
gp  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.


Galileo Computing

3.4.1 Klassen und Interfaces  downtop

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

gp  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.

Galileo Computing

3.4.2 Variable  downtop

Icon
Streng typisiert: Keine Variable ohne Typ

Java ist eine streng typisierte Sprache. Dies bedeutet:

gp  Jede Variable muss vor ihrer Benutzung deklariert und einen Wert zugewiesen bekommen.
gp  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
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:

gp  Bei Variablen vom primitiven Typ ist der Wert der Variablen konstant.
gp  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

gp  Klassen-Variablen, die nur eimal pro Klasse vorkommen und
gp  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!

Galileo Computing

3.4.3 Methode  downtop

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

gp  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.
gp  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

Zur Signatur zählt nach Java-Konvention nicht der Rückgabetyp.13  Es gilt folgende Regel:

Signaturen: Unterscheidungsmerkmal für Methoden

gp  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
}

Galileo Computing

3.5 Aufruf einer Methode  downtop

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.


Galileo Computing

3.5.1 Argumente und Wertübergabe  downtop

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
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 

gp  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

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!

gp  Ändert man den Referenzwert innerhalb der Methode, ändert dies nicht den Referenzwert des Arguments außerhalb der Methode.
gp  Ä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:

gp  Die Referenzen iv2 und iv3 zeigen auf dasselbe Array, d.h., die int-Elemente wurden durch den Aufruf geändert (zweite Regel).
gp  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;

Galileo Computing

3.5.2 Einfacher Methoden-Aufruf  downtop

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.


Galileo Computing

3.5.3 Qualifizierender Methoden-Aufruf  downtop

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

Statische Methoden

gp  können nicht auf die Instanz-Variablen der Klasse zugreifen, da diese nur in den Objekten der Klassen existieren.
gp  können keine Instanz-Methoden der Klasse aufrufen.
gp  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

Statische Methoden können zwar auch über Instanzen aufgerufen werden. Dies ist aber irreführend und sollte unbedingt vermieden werden.



Galileo Computing

3.6 Zusammenfassung  downtop

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.


Galileo Computing

3.7 Testfragen  toptop

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.

  

Perl – Der Einstieg




Copyright © Galileo Press GmbH 2001 - 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken und speichern. Ansonsten unterliegt das <openbook> denselben Bestimmungen wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.
Die Veröffentlichung der Inhalte oder Teilen davon bedarf der ausdrücklichen schriftlichen Genehmigung von Galileo Press. Falls Sie Interesse daran haben sollten, die Inhalte auf Ihrer Website oder einer CD anzubieten, melden Sie sich bitte bei: stefan.krumbiegel@galileo-press.de


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de