back to top   1 Syntax-Elemente

 

Bisher bereits im Vorübergehen gestreift und einführend behandelt, sollen im folgenden einige Java-Grundlagen vertieft werden.

Anweisungen
Eine Java-Anweisung wird aus einer Zeichenmenge, die mit dem Semikolon (;) abgeschlossen wird gebildet. Bei der Eingabe des Java-Quellcodes müssen keine besonderen Formatierungsvorschriften (über die Berücksichtigung der Groß- und Kleinschreibung hinaus) eingehalten werden. Eine syntaktisch korrekte Java-Anweisung kann an beliebiger Spaltenstelle beginnen. Auch muss nicht zwingend für jede Anweisung eine neue Zeile genutzt werden; lediglich die Kennzeichnung des Anweisungsendes mit dem Semikolon ist verpflichtend. Zwischen den einzelnen Komponenten einer Anweisung können belibige Leerzeichen, Tabulatoren oder Zeilenendezeichen stehen. Mindestens ein sog. white space ist jedoch zur Abgrenzung der einzelnen Anweisungssegmente zwingend notwendig.
Aufgrund dieser Freiheitsgrade wird Java auch als format-freie Sprache bezeichnet.
In der Praxis haben sich jedoch Einrückungen als Strukturierungsmittel zu Nutzen der Übersichtlichkeit und die platzierung der Anweisungen in jeweils eigenen Zeilen bewährt.
Zusätzliche Leerzeichen steigern zwar den Platzbedarf des Quellcodes, werden jedoch vom Compiler ignoriert, und wirken sich nicht nachteilig auf das Verhalten (Ablaufgeschwindigkeit, Speicherplatzbedarf) des übersetzten Bytecodes aus.

Ausdrücke
Liefert eine Anweisung einen Wert zurück, so wird sie als Ausdruck bezeichnet.
Rückgabewerte (engl. return value) können durch Programmkonstrukte abgefragt -- und auf sie reagiert -- werden. Hierzu müssen die durch den Ausdruck retournierten Werte ausgewertet (siehe III.3.1) oder in einer Variablen abgelegt werden.
Ist der Rückgabewert nicht von Interesse so kann er ignoriert werden.

Blockstrukturen
Geschweifte Klammerung ({ }) umschliessen Blöcke. Ein solcher Block besteht aus einer Menge konkreter Anweisungen, die wie eine einzige Anweisung behandelt werden.

Variablen
Analog zur Definition der Eigenschaften einer Klasse durch Attribute (--> II.2.2.3) können auch innerhalb von Methoden benannte Speicherplätze -- sog. Variablen -- zur Aufnahme von veränderlichen Werten definiert werden. Diese Speicherplätze werden bei jedem Methodeneintritt (durch Aufruf der zugehörigen Operation) neu angelegt und beim Verlassen der Methode wieder freigegeben. Dies bedeutet: in Variablen abgelegte Werte sind nach Methodenaustritt verloren. Als synonym verwendete Bezeichnung hat sich der aus der prozeduralen Programmierung übernommene Terminus lokale Variablen eingebürgert.
Syntaktisch geschieht die Variablendefinition wie die der Attribute, durch Angabe des Datentyps, gefolgt vom eindeutigen Namen des Speicherplatzes. Hinsichtlich der Benennung gelten dieselben lexikalischen Restriktionen wie für Attribute.

back to top   2 Kommentierung mit Java-Doc

 

Kommentare
Kommentare -- erklärender Text innerhalb des Quellcodes -- stellt primär unproduktiven Code dar. Jegliche Kommentare werden beim Compilierungsvorgang ignoriert und finden keinen Eingang in den übersetzten Bytecode.
Die Kommentierung erhöht jedoch die Lesbarkeit des entstehenden Programmtextes und vereinfacht damit den späteren Nachvollzug des zugrundeliegenden Gedankengangs für Dritte. Darüberhinaus werden in Kommentaren wichtige Details zu gewählten Algorithmen und Lösungswegen dokumentiert, um spätere Wartungsarbeiten und Weiterentwicklungen zu erleichtern.

Java unterstützt drei verschiedene Kommentierungsmöglichkeiten durch spezielle Kommentarformate:

Beispiel 1 (Quellcodedatei: KommentarDemo.java)
/**
Allgemeine Beschreibung der Klasse...
@see Verweis auf anderes Package oder andere Klasse
@author Mario Jeckle
@version Erster Versuch 0.1ß
*/
public class KommentarDemo //dies ist ein einzeiliger Kommentar
{
   /**
    Beschreibung zum Attribut Wert, das mit 42 initialisiert wird...
    @see "Douglas Adams: Per Anhalter durch die Galaxis, S. 158"
   */
   protected int Wert = 42;
   
   public static void main(String[] argv)
      /* Die main-Methode bildet den Startpunkt jedes Java-Programms
      deshalb darf sie nicht vergessen werden */
      {
         //hier steht die Implementierung...
   } //end main() -- Ende der main-Methode
   
   /**
    @param Zwei ganzzahlige Summanden vom Datentyp int
    @return Summe der beiden Zahlen
    @see "Zur Definition der Summe von ganzen Zahlen siehe Bronstein, Semendjajew"
   */
   public static int berechneSumme(int summand1, int summand2)
   {
      return summand1+summand2;
   } //end berechneSumme
}// end class KommentarDemo -- auch das ist ein Kommentar

Der JavaDoc-Generator berücksichtigt u.a. folgende Kommentare (eine vollständige Aufstellung kann der JDK-Dokumentation entnommen werden):

Tag
Bedeutung
@autor
@deprecated
Veraltete, aber (aus komatibilitäsgründen) noch im Quellcode enthaltene, Funktionalität von deren weiterer Nutzung durch den Programmierer jeedoch abgeraten wird.
@exception
@throws
Ausnahmebedingungen dei während der Ausführung einer besstimmten Methode auftreten können.
@param
Übergabeparameter einer Operation
@return
Rückgabewert der Operation.
@see
Verweis auf eine andere Klasse, ein Package oder sonstige weiterführende Information.
@version
Versionsidentifikation des Programms.

Hinweis: Die gesamte Dokumentation zum JDK von Sun wurde ausschiesslich aus dem Java-Quellcode und den darin abgelegten formalen JavaDoc-Kommentaren automatisch generiert.

Der JavaDoc-Generator erstellt nach dem Aufruf mit: javadoc -version -author -private KommentarDemo.java verschiedene Dokumentationsdateien im HTML-Format.
Nachfolgend die erstellten Seiten für das Programm aus Beispiel 1

back to top   3 Operatoren

 

Operatoren dienen zur Manipulation bereitgestellter Werte. Ergebnis einer Operator-Anwendung ist ein Resultat eines feststehenden Typs. Die zu manipulierenden Eingangswerte, Operanden genannt, müssen eine Ausprägung eines zum Operator kompatiblen Typs sein. Mit anderen Worten: der Operator muss als Verknüpfung auf den Eingabedaten definiert sein.
Java bietet zunächst die -- einfachen -- bekannten binären arithmetischen Operationen:
Addition    +
Subtraktion   -
Multiplikation   *
Division   /
Modulus   %
Diese Operatoren sind auf den ganzzahligen Primitivtypen byte, short, int, long ebenso wie auf den Fließkommatypen float und double, nicht jedoch für Wahrheitswerte, definiert. Darüberhinaus sind die arithmetischen Grundoperationen auch auf char Datentypen anwendbar, denn diese werden hierzu als 16-Bit Zahlen (analog short) behandelt.
Hinsichtlich des gelieferten Ergebnistyps geltelten folgende Festlegungen:

Als binäre Vergleichs-Operatoren stehen zur Verfügung:

Operator
Bedeutung
==
Gleichheit
Hinweis: Beachten Sie den Unterschied zum Zuweisungsoperator =
!=
Ungleichheit
<
Kleiner als
>
Größer als
<=
Kleiner oder gleich
>=
Größer oder gleich

Zur direkten Manipulation der den Primitivtypen zugrundeliegenden Binärzahlen werden folgende bitweise agierenden Operatoren angeboten:

Operator
Bedeutung
&
Bitweises Und
|
Bitweises Oder
^
Bitweises Xor (a^b = (a|b) & (!a|!b))
!
logische Negation>
<<
Bitverschiebung nach links (entspricht vorzeichenloser Multiplikation mit 2)
>>
Bitverschiebung nach rechts (entspricht vorzeichenloser Division durch 2)
>>>
Bitverschiebung nach rechts und einfügen notwendiger führender Nullen
~
Bitweises Komplement

Ergänzend definiert Java, in Anlehnung an die Programmiersprache C, noch einige abkürzende Schreibweisen für gebräuchliche Operationen und Zuweisungen:

Operator
Bedeutung
++
Postinkrement
Wert der Variablen wird nach Abarbeitung des Ausdrucks erhöht
--
Präinkrement.
Wert der Variablen wird vor Abarbeitung des Ausdrucks erniedrigt
+=
Addition mit anschliessender Neuzuweisung
a=a+5 entspricht: a+=5
-=
Subtraktion mit anschliessender Neuzuweisung
a=a-5 entspricht: a-=5
*=
Multiplikation mit anschliessender Neuzuweisung
a=a*b entspricht: a*=b
/=
Division mit anschliessender Neuzuweisung
a=a/b entspricht: a/=b
<<=
Bitverschiebung nach links mit anschliessender Neuzuweisung
a = a << 2 entspricht: a<<=2
>>=
nach rechts mit anschliessender Neuzuweisung
a = a >> 2 entspricht: a>>=2
>>>=
Bitverschiebung nach rechts mit Nullfüllung und anschliessender Neuzuweisung
a = a >>> 3 entspricht: a>>>=3
&=
Logisches Ver-Und-en und anschliessende Neuzuweisung
a = a & b entspricht: a&=b
|=
Logisches Ver-Odern und anschliessende Neuzuweisung
a = a | b entspricht: a|=b
^=
Logisches exklusives Oder und Neuzuweisung
a = a ^ b entspricht: a^=b

Operatorpräzedenz:
Analog der bekannten Regel Punkt vor Strich sind in Java ebenfalls Vorrangsregeln für die vorgestellten Operatoren definiert. Zunächst werden die Operatoren gemäß der folgenden Übersicht ausgewertet. Operationen mit Operatoren derselben Präzedenzstrufe in einer Zeile werden von links nach rechts ausgewertet.

Operatoren in absteigener Auswertungsreihenfolge
Anmerkung
. [] ()
Der Punkt zum Zugriff auf öffentliche Komponenten (Operationen oder Attribute) einer Klasse oder eines Objekts. Für runde Klammern innerhalb eines Ausdrucks gelten die aus der Arithmetik bekannten Regeln. Die eckigen Klammern finden für Arrays Anwendung.
++ -- ! ~ instanceof
new (Type) Expression
Objekterzeugung, nach evtl. erfolgter Typkonversion
* / %
Multiplikation, Division, Modulus
+ -
Addition und Subtraktion
<< >> <<<
Bitweise Operationen
< >
Vergleiche
== !=
Äquivalenzrelationale Operatoren
&
Und
^
Xor
|
Oder
&&
logisches Und
||
logisches Oder
? :
Kurzform für if...then...else (--> III.3.1)
* += -= *= /= &= ^=
Zuweisungen
|= <<= >>= >>>=
Und

back to top   4 Kontrollstrukturen zur Ablaufsteuerung

 

Das in Methoden ausprogrammierte dynamische Verhalten (der Programmablauf) kann durch verschiedene Kontrollstrukturen beeinflusst und gesteuert werden. "Früher" war der alternative Begriff Kontrollfluss für die Abarbeitungsschritte eines Programms gebräuchlich, jedoch führt dessen Verwendung im Kontext potentiell mehrerer (unabhängiger) Kontrollflüsse eines Programms zu Verwirrungen. Wir werden deshalb den Begriff Ablaufsteuerung (engl. flow of control) bevorzugen.

Erst die Mittel zur Steuerung des Ablaufs "erwecken ein Programm zum Leben". Denn erst mit ihnen werden nicht-deterministische Elemente (d.h. Elemente deren Verhalten nicht statisch vorhersagbar ist) eingeführt. Mit ihnen ist die Prägung dynamischen Verhaltens, durch reagieren auf konkrete Situationen im Programm möglich.

4.1 Die Selektion/Das if-Statement

Semantik:
Oftmals ist es erforderlich an Bedingungen geknüpfte Entscheidungen zu treffen. Konkret wird anhand einer angegebenen (für die Maschine auswertbaren) Bedingung entschieden, ob diese erfüllt ist oder nicht. Abhängig vom Ausgang dieser Bedingungsauswertung wird entweder die angegebene Alternative für den "wahr-Fall" (die Bedingung ist erfüllt) oder den "falsch-Fall" (Bedingung ist nicht erfüllt) weiter verfolgt.
Technisch gesprochen: Der Ablaufsteuerung werden, abhängig von einer im Programm auswertbaren Bedingung, zwei alternative Fortsetzungen angeboten.

Java-Syntax:
Die Bedingungsauswertung wird in Java durch das if-Konstrukt realisiert.


if (Bedingung)
   {
      //true-Fall: Bedingung ist erfüllt
   }
else
   {
      //false-Fall: Bedingung ist nicht erfüllt
   }

Der mit else eingeleitete Alternativteil ist optional, und muss nicht zwingend angegeben werden. Ist eine ohne Else-Teil spezifizierte Bedingung falsch, so wird das Programm mit der nächsten auf die if-Anweisung folgenden Anweisungszeile fortgesetzt.
Hinweis: Die Blockstruktur, welche die true- und false-Alternativen klammen müssen (strenggenommen) nur angegeben werden, wenn die beiden Alternativen jeweils aus mehr als einer Anweisung bestehen. Jedoch hat es sich in der Praxis als ratsam erwiesen auch Alternativen, aus nur einer einzigen Anweisung in Block-Klammern einzuschliessen, um eine potentielle spätere Fehlerquelle bei Erweiterung des angegebenen Codes der Alternativen von vorherein auszuschliessen.
Denn:


byte a = 1;
if (a == 1)
   System.out.println("a ist gleich 1, setzte a auf 0");
   a = 0;
else
   System.out.println("a ist gleich 0, setzte a auf 1");
   a = 1;
System.out.println("Wert von a="+a);

Diese Anweisungsfolge führt -- anders als beabsichtigt, und zu erwarten, zunächst zu einem Compilierungsfehler: (als Programmname ist iftest.java angenommen)


iftest.java:10: 'else' without 'if'.
      else
      ^

Der Java-Compiler hat bei Überprüfung der Java-Syntax zum Übersetzungszeitpunkt festgestellt, dass eine else quasi "mitten" im Programm auftritt. Dies ist das Resultat der fehlenden Blockklammern. Da die Java-Syntax zwischen if und else genau ein Statement erlaubt (Zur Erinnerung: Ein in geschweifte Klammern eingeschlossener Block wird wie eine einzige Anweisung behandelt) zweite auf den Bedingungsteil folgende Anweisung als Fortsetzung des Programms (und damit implizit als Ende der if-Anweisung) behandelt. Und "mitten im Programm" (lies: außerhalb von if-Anweisungen) dürfen keine else-Zweige auftreten.

Beispiel 2 einer korrekten if-Anweisung:


      if (a == 1)
      {
         System.out.println("a ist gleich Eins");
      } //end if-clause
      else
      {
         System.out.println("a ist ungleich Eins");
      } //end else-clause

Zugelassende Bedingungen
sind alle Java-Ausdrücke, die als Boole'scher Ausdruck interpretiert werden können. Das sind somit:

Der von der Methode gelieferte Rückgabewert wird dann im Programmablauf an die Stelle des konkreten Aufrufs platziert. Selbstverständlich können ausschliesslich solche Methoden in Bedingungen zur Auswertung angegeben werden, die als Rückgabewert den Primitivtyp boolean liefern. (Anmerkung: auch der Wrapper-Type java.lang.Boolean ist nicht gestattet).
Ergänzend sei noch die (selbstverständlich existierende) Möglichkeit des Vergleichs von Konstanten-Termen sowie einfacher Literale hervorgehoben. Diese aus Sicht der Logik uninteressanteste Variante wird in der Praxis oft zur Entwicklungszeit eingesetzt, um Bedingungen schnell zu testen.
Beispiele: Die Bedingung if (1 == 1) ist immer wahr, ebenso ist die Kurzform if (true) gebrächlich.

In einer If-Anweisung können mehrere Bedingungen gebündelt werden:
Kann die Entscheidung für eine der beiden angebenen Alternativen nur auf Basis mehrerer auswertbarer Bedingungen getroffen werden, so können diese -- mit den bekannten logischen Operatoren verknüpft -- angegeben werden.

Beispiel 3:


int a = 11;

if (a >10 && isPrime(a) == true && 2 == 2)
   {
   System.out.println("a ist eine Primzahl, die grösser als 10 ist");
   } //end-if

Das Beispiel 3 zeigt eine Bedingung, in der sowohl eine (im Beispiel 3 nicht dargestellte Methode (mit Rückgabewert boolean) isPrime aufgerufen wird, als auch eine (immer wahre) Konstantenauswertung (2==2) vorgenommen wird.
Mit dieser Mimik läßt sich auch die oft benötigte Wertebereichsprüfung (z.b. in get- und set-Methoden) einfach realisieren: Beispiel 4:
Ein Attribut budget einer Klasse Projekt darf nur zwischen 100 und 1000 liegende Werte annehmen (100<=budget>=1000). Beim Versuch einen ungültigen Wert zu setzen soll eine Fehlermeldung ausgegeben werden:


class Projekt
{
   int budget;

   void setBudget(int newBudget)
   {
      if (newBudget <= 1000 && newBudget >= 100)
      {
         budget = newBudget;
      } //end-if
      else
      {
         System.out.println(newBudget+" liegt außerhalb der erlaubten Grenzen");
      } //end-else
   } //end setBudget
} //end class Projekt

If-Anweisungen können auch geschachtelt werden
Innerhalb einer der beiden nach der Bedingung aufgeführten Alternativen gelten die selben syntaktischen Regeln wie im umgebenden Java-Programm. Das bedeutet insbesondere es dürfen weitere Alternativen, die erst nach vorhergehender Auswertung der im If-Teil formulierten Bedingung geprüft werden sollen, als vollständige If-Statements angegeben werden.

Beispiel 5:


if (schulnote == 1)
{
   System.out.println("sehr gut");
} //end-if
else
{
   if (schulnote == 2)
   {
      System.out.println("gut");
   } //end-if
   else
   {
      if (schulnote == 3)
      {
         System.out.println("befriedigend");
      } //end-if
      else
      {
         if (schulnote == 4)
         {
            System.out.println("ausreichend");
         } //end-if
         else
         {
            if (schulnote == 5)
            {
               System.out.println("mangelhaft");
            } //end-if
            else
            {
               if (schulnote = 6)
               {
                  System.out.println("ungenügend");
               } //endif
               else
               {
                  System.out.println("ungültiger Wert");
               } //ene-else
            } //end-else
         } //end-else
      } //end-else
   }//end-else
}//end-else

Bei Analyse des Codes fällt zunächst dessen Unübersichtlichkeit ins Auge. Darüberhinaus zeigt sich der notwendige Aufwand zur Ermittlung des Fehlerfalles. Zwar hätte sich dies mit einer zusätzlichen Überprüfung vor Untersuchung auf den tatsächlichen Wert realiseren lassen, jedoch würde durch dieses Vorgehen lediglich das letzte else eingespart.

Ergänzung: der Bedingungsoperator
Eine Alternative zur Verwendung der Schlüsselworte if und else bietet eine von C übernommene verkürzte Schreibweise. Sie erlaubt jedoch die Einflussnahme auf den Rückgabewert. Während dieser bei allen If-Konstrukten immer true oder false ist, kann er hier beliebig gesetzt werden.
Syntaktisch handelt es sich um einen ternären Operator, der neben der Bedingung auch die beiden möglichen Rückgabewerte als Parameter erwartet. Sein Aussehen kann wie folgt beschrieben werden:
Test ? Ergebnis im true-Falle : Ergebnis im false-Falle

Beispiel 6:
int smaller = x /lt; y ? x : y; Als Resultat wird der Variablen smaller ein Wert zugewiesen. Im true-Falle (d.h. es gilt x < y) ist dies x, andernfalls (d.h. es gilt x > y oder x = y) ist dies y.

Jede mit dem Bedingungsoperator formulierte Anweisung läßt sich ohne Informationsverlust in eine If-Anweisung überführen.
Im Beispiel 6 wäre dies:


if (x < y)
{
   smaller = x;
} //end-if
else
{
   smaller = y;
} //end-if

Der Bedingungsoperator führt zwar zu kompakterem Quellcode, jedoch zu Lasten der Les- und Nachvollziehbarkeit. Im Hinblick auf spätere Wartungen und Erweiterungen des Programmtextes sollte auf dieses Konstrukt verzichtet werden.

Die Mehrfachselektion/Das switch-Statement

Semantik:
Ein gut geeignetes Mittel zur einfachen und sicheren Realisierung von mehrfachen Fallunterscheidungen stellt die swich-Anweisung dar.
Sie ist im wesentlichen eine Verallgemeinerung des if-Statements.

Java-Syntax:


switch (Bedingung)
{
   case Alternative1:
      Anweisungen...
      break;
   case Alternative2:
      Anweisungen...
      break;
   ... //weitere case-Anweisungen
   default:
      Standardfall
} //end-switch

Die im switch-Konstrukt mögliche Bedingung erweitert die Fähigkeiten der if-Anweisung dahingehend, dass die Bedingung nicht mehr ausschliesslich als Boole'sche Operation interpretiert wird, sondern beliebige int-Werte als Bedingungsergebnis zugelassen sind. Da jedoch auch einzelne Zeichen (Primitivtyp char in Java intern wie eine Ganzzahl vom Primitivtyp short behandelt werden, kann die Auswertung auch hinsichtlich eines einzelnen Zeichens geschehen.
Auffallend an der Syntax des switch-Statements ist der Verzicht auf die von der if-Anweisung her gewohnten Blockstrukturen. Stattdessen werden die einzelnen Alternativen durch ein break abgegrenzt. Die Gründe hierfür liegen in der engen Verwandtschaft von Java zu C; bereits dort existiert das syntaxgleiche Konstrukt. Aber diese Syntax hat auch ihr gutes: Sie vermittelt uns einen guten Einblick in die Abarbeitung der Anweisung durch die Maschine. Anstatt wie beim if vollständige Blöcke vorzugeben, von denen entweder der erste oder der zweite abgearbeitet wird haben wir es hier mit potentiell 216 Alternativen (eben dem vollständigen Werteumfang des Primitivtypen int zu tun). Das den Aufwand, der beim Kaskadieren der else-Blöcke gezeigt wurde, zu vermeiden, (und damit die Ausführungsgeschwindigkeit zu erhöhen) wird der benötigte Code-Teil direkt angesprungen. Die benötigte Sprungtabelle läßt sich durch Entnahme der (statischen) Fälle aus dem Code -- sie sind nach dem Schlüsselwort case angegeben -- leicht aufbauen. Ist der zutreffende Code einmal lokalisiert, so wird er ausgeführt, bis der Interpreter auf ein weiteres Schlüsselwort trifft. Dieses mit break bezeichnete Schlüsselwort weisst den Interpreter an, den Kontrollfluss wieder zurück auf die nächste auf die komplette switch-Anweisung folgende Programmzeile zu setzen. Die benötigte Programmzeile ist diejenige, die auf die schliessende geschweifte Klammer folgt.
Zugegebenermassen ist es nicht zwingend notwendig, derart maschinennahe Betrachtungen soweit zu betonen, dass sie sich im Quellcode der höheren Programmiersprache niederschlagen. Im Falle von Java wurde jedoch dieses Konstrukt aus "humanen Kompatibilitätsgründen", d.h. um existierenden Programmierern (mit C-Erfahrung) den Umstieg auf Java zu erleichtern, beibehalten.
Jedoch darf der durch break gekennzeichnete Abschluss einer Alternative nicht weggelassen werden, da sonst die Ausführung bis zum Auftreten des nächsten breaks, sogar über die anderen Alternativen hinaus, fortgesetzt würde. Hinweis: Besonders heimtückisch ist das Vergessen eines einzigen Breaks inmitten einer Menge von Alternativen, dies führt zu unschönen Ausführungsfehlern, da das Programm syntaktisch korrekt ist. Achten Sie bei der Erstellung Ihres Codes immer auf die korrekte Terminierung der einzelnen Alternativen einer switch-Anweisung. Das gezielte durchlaufen des nachfolgenden Falles wird mit fall through bezeichnet, und mag für manche Situationen wünschenswert sein. In allen Fällen führt es jedoch zu unübersichtlicherem Code, da der beabsichtigte Kontrollfluss nicht mehr eindeutig erkennbar ist.
Durch default wird der "Sonst-Fall" eingeleitet. Diese Alternative wird bei einem Bedingungswert für den keine explizite Alternative spezifiziert wurde angesprungen.

Beispiel 7 (Umsetzung des Beispiels 5):


switch (schulnote)
{
   case 1:
      System.out.println("sehr gut");
      break;
   case 2:
      System.out.println("gut");
      break;
   case 3:
      System.out.println("befriedigkend");
      break;
   case 4:
      System.out.println("ausreichend");
      break;
   case 5:
      System.out.println("mangelhaft");
      break;
   case 6:
      System.out.println("ungenügend");
      break;
   default:
      System.out.println("ungültiger Wert");
} //end-switch

Prinzipiell ist es möglich jedes if-Statement in eine gleichmächtige switch-Anweisung zu überführen. Hierbei ist lediglich auf die notwendige Abbildung des Boole'schen Ergebnistyps auf die fuer switch benötigte int-Zahl zu achten. Das nachfolgende Beispiel 8 illustriert ein mögliches Vorgehen in diesem Zusammenhang:

Beispiel 8:


public class switchTest
{
   public static void main(String[] argv)
   {
      final int TRUE = 1231;
      final int FALSE = 1237;


      Boolean bedingung = new Boolean( 5 > 7);

      switch ( bedingung.hashCode() )
      {
         case TRUE:
            System.out.println("true");
            break;
         case FALSE:
            System.out.println("false");
            break;
       } //end-switch
   } //end main()
}//end switchTest

Anmerkungen:
Das Beispiel 8 nutzt die hashCode-Methode auf Objekten der Standardklasse Boolean (dem Wrapper-Typ für boolean), die eine Abbildung des Wahrheitswertes auf einen int-Wert erlaubt. Die im If-Teil der Selektionsanweisung platzierte Bedingung wird in Beispiel 8 als Wert einem neuen Boolean-Objekt zugewiesen. Der Wahrheitswert wird mittels der erwähnten Standardmethode in eine eindeutige int-Repräsentation abgebildet.
Die beiden Konstanten dienen lediglich der besseren Lesbarkeit, da hashCodetrue auf 1231 bzw. false auf 1237 abbildet.
Der default-Fall kann hier entfallen, der gesamte mögliche Wertebereich von Boolean durch explizite Alternativen abgedeckt wird.

4.2 Die Iteration/Schleifenkonstrukte

Schleifen dienen der mehrfachen Ausführung identischer Codeteile. Durch entsprechende Belegung Schleifen-interner Variablen, kann so bei jedem Schleifendurchlauf verschiedenes Verhalten realisiert werden.

4.2.1 Fixe Schleifen/For-Schleifen

Semantik:
Eine For-Schleife erlaubt eine genau festgelegte Anzahl von Schleifendurchläufen.

Java-Syntax:


for (Initialisierung; Fortsetzungbedingung; Inkrement)
{
   Anweisungen
}

Beispiel 9:


for (i = 1; i < 10; i=i+1)
{
   System.out.println(i);
}//end-for

Ausgabe:


1
2
3
4
5
6
7
8
9

Hinweis:
Die Zahl 10 wird nicht ausgegeben, da nach Erhöhung des Schleifenzählers (nach dem neunten Durchlauf) auf 10 die Fortsetzungsbedingung mit 10 < 10 nicht mehr erfüllt.

Angabe mehrerer Schleifenparameter
Durch Spezifizierung mehrerer Werte für die einzelnen Schleifenparameter (Initialisierung, Fortsetzungsbedingungen, Inkremente) können auch komplexere Schleifen realisiert werden. In diesem Fall werden die einzelnen Komponenten durch Komma voneinander getrennt.

Beispiel 10:


public class fortest
{
   public static void main(String[] argv)
   {
      int i;
      int a;
      int b;
      for (i=1, a=1, b=1; a>=b; i++, a=i*10, b=i*i)
      {
         System.out.println(i+" mal 10 ist größer als "+i+" Quadrat");
      } //end-for
   }//end main()
}//end class fortest1

Ausgabe:


1 mal 10 ist größer oder gleich als 1 Quadrat
2 mal 10 ist größer oder gleich als 2 Quadrat
3 mal 10 ist größer oder gleich als 3 Quadrat
4 mal 10 ist größer oder gleich als 4 Quadrat
5 mal 10 ist größer oder gleich als 5 Quadrat
6 mal 10 ist größer oder gleich als 6 Quadrat
7 mal 10 ist größer oder gleich als 7 Quadrat
8 mal 10 ist größer oder gleich als 8 Quadrat
9 mal 10 ist größer oder gleich als 9 Quadrat

Die einzelnen Schleifenparameter sind optional. So erzeugt for(;;) eine Endlosschleife ohne Schleifenzähler

4.2.2 Schleifen mit Abbruchbedingung

Das Beispiel 10 zeigt die Mächtigkeit der For-Schleife, hinsichtlich der Formulierung leistungsfähiger Abbruch- bzw. Fortsetzungsbedingungen. Ist ein Schleifenzähler nicht notwendig, so bieten sich als Varianten Schleifen mit expliziter Abbruchbedingung an.
Dieser Schleifentyp wird durch die while- und do-Schleifen zur Verfügung gestellt. Als Besonderheit bietet dieser Schleifentyp zwei unterschiedliche Syntaxvarianten an.

Java-Syntax:


do
{
   //Anweisungen
} while (Fortsetzungsbedingung);

oder alternativ:


while (Fortsetzungsbedingung)
{
   //Anweisungen
}

Je nach Stellung der im while-Teil formulierten Fortsetzungsbedingung wird zwischen kopfgesteuerten-Schleifen (im Falle der Auswertung vor dem Schleifendurchlauf) bzw. fußgesteuerten-Schleifen (im Falle der Auswertung nach dem Schleifendurchlauf) unterschieden. Für die Fortsetzungsbedingungen gelten dieselben syntaktischen Festlegungen wie bei For-Schleifen.
Als Hauptunterschied sei folgendes festgehalten:

4.3 Sprunganweisungen

Der bekannteste Repräsentant der Familie der (unbedingten) Sprunganweisungen ist das GOTO-Konstrukt. Eigentlich sind diese Arten der Einflussnahme auf den Programmablauf eher verpönt (vgl. Edsger Dijkstras (mittlerweile lägendären) Leserbrief Go To Statement Considered Harmful (in: Communications of the ACM, March 1968, Vol. 11, No. 3, pp. 147-148)).

Prinzipiell lassen sich alle notwendigen Ablaufsteuerungsschritte mit den bekannten Sprachelementen Selektion (if, auch als bequemere Mehrfachselektion (switch)), Iteration (die verschiedenen Schleifenkonstrukte wie for, do..while bzw. while...do) ausdrücken. Der bequeme unbedingte Sprung (engl. unconditional jump oder unconditional branch) stellt somit nur eine Vereinfachung eines bedingten Sprunges mit immer-wahrer Bedinung dar.

Das break-Schlüsselwort zum Verlassen einzelner Alternativen einer Mehrfachselektion wurde bereits in Abschnitt III.1.4.1 diskutiert. Genaugenommen handelt es sich bei break um einen unbedingten Sprung aus der aktuellen Struktur in die umgebende. Das Beispiel der switch-Anweisung zeigt, dass es sich dabei nicht zwingend um explizite Blockstrukturen handeln muss.
Durch break können auch beliebige Schleifen vor eintreten der Abbruchbedinung verlassen werden.
Mit Ausnahme der Anwendung als Terminierung einer Alternative innerhalb einer switch-Struktur, die keine Alternativsyntax zulässt und vorsieht, sollte jedoch auf den Gebrauch von break verzichtet werden, und eine Bedingung zum vorzeitigen Verlassen einer Schleife entweder in einen Fehlerfall überführt (und dann durch Exception Handling abgearbeitet werden) oder in die Abbruchbedingung der Schleife integriert werden.

Beispiel 11:


for (int i=0; i<10; i++)
{
   System.out.println( i );
   if ( i==7 )
   {
      break;
   } //endif
} //end for

Die Abarbeitung der Schleife wird beim Zählerstand 7 abgebrochen.

Die Anweisung continue veranlasst das System alle in einer Schleife folgenden Anweisungen zu ignorieren und unverzüglich die boole'sche Fortsetzungsbedingung auszuwerten.

Beispiel 12:


for (int i=0; i<10; i++)
{
   if ( i==7 )
   {
      continue;
      System.out.println("test"); // never gets here!
   } //end if
   else
   {
      System.out.println( i );
   } //end else
} //end for

Die Schleife gibt alle Zahlen, mit Ausnahme der 7 am Bildschirm aus. Die Übersetzung liefert für Zeile 6 jedoch den Fehler Statement not reached., da alle auf continue folgenden Anweisungen ignoriert -- und folglich niemals ausgeführt -- werden.

Zum Abschluss sei noch das bereits bekannte return-Statement in den Zusammenhang der Sprunganweisungen gestellt. Mit ihm kann jede mit Rückgabewert deklarierte Methode an beliebiger Stelle verlassen werden. Dem Aufrufer wird der Wert des auf return folgenden Ausdrucks übergeben. Der Compiler prüft an dieser Stelle die notwendige Typkorrektheit.

Beispiel 13


int compareInt(int a, int b)
{
   if ( a<b )
   {
      return a;
   }
   else
   {
      if ( b<a )
      {
         return b;
      } //endif
      else
      {
         return 0;
      } //end else
   }//end else
} //end compareInt

Aufruf: int result = compareInt(3,4);

Die Methode compareInt liefert bei Ungleichheit der übergebenen Parameter den kleineren, andernfalls 0, an den Aufrufer zurück. Je nach Gültigkeit der geprüften Bedingung wird das if-Konstrukt und die umgebende Methode mit den spezifizierten Rückgabewert verlassen.

back to top   5 Ausnahmen und ihre Behandlung/Exception Handling

 

Bei der Disskussion der Rückgabewerte einer Methode sind wir bereits auf mögliche Fehlersituationen und ihre Kommunikation an den Aufrufer eingegangen. Dort zeigte sich auch erstmals die Problematik im Allgemeinen zwischen erfolgreicher Ausführung -- verbunden mit dem entsprechenden zurückgelieferten Ergebnis -- und fehlerhafter Abarbeitung -- mit dem entsprechenden Fehlercode -- zu unterscheiden. Relativ leicht ist uns dies bei Methoden vom Typ boolean gefallen, da sich dort über true und false eine intuitive Semantik herauslesen lässt (etwa: true bei erfolgreicher Abarbeitung, und false für die Gesamtheit der möglichen Fehler).
Als problematischer erweisen sich alle Methoden mit anderen Rückgabewerten (etwa die Primitivtypen, aber auch Objekte). Hierbei fällt es zunehmend schwer genau einen Rückgabewert (z.B. 0) durchgehend als Fehlerkennzeichnung durchzuhalten. Denken Sie hierbei an diverse mathematische Formeln, die Null als legitimes Ergebnis liefern können.
Zusätzlich zeigt sich schon bei kleineren Programmierprojekten, dass die Übersichtlichkeit des entstehenden Codes durch die Vermischung von Ablauflogik und Fehlerkontrolle deutlich leidet.
Intuitiv ist auch die Möglichkeit des Auftretens identischer Fehler (d.h. Fehler identischer Ursache) an verschiedenen Stellen der Programmausführung klar. Das bedeutet, in der Umsetzungsphase wird es u.U. notwendig gleiche Fehlerabfragen und -behandlungen an verschiedenen Stellen auszucodieren. Die damit einhergehende Redundanzproblematik, von der unnötigen Aufblähung des Quellcodes einmal abgesehen, offenbart sich spätestens in späteren Wartungszyklen.
Abschliessend sei noch auf die Problematik des Erfassens und Reagierens auf alle denkbaren, d.h. praktisch möglichen, Fehlersituationen hingewiesen. Denn prinzipiell ist die Anzahl der möglichen Fehlschlagsursachen einer Methode zwar endlich, jedoch immernoch sehr viel, so dass oftmals ein einziger Rückgabewert nicht die angestrebte Aussagekraft besitzt -- und besitzen kann.
Jedoch ist eine möglichst genaue Information über die Fehlerursache für die weitere Behandlung unabdingbar.

Deshalb ist es naheliegend nach einem Mechanismus zu suchen der die genannten Kriterien erfüllt. Im einzelnen sind dies:

In den meisten objektorientierten Programmiersprachen, so auch in Java, wird aus diesem Grunde ein solcher Mechanismus eingeführt.
Hauptprimitive ist hierbei die Ausnahme (engl. Exception). Konzeptionell ist sie vergleichbar mit den Botschaften (vgl. Kapitel 3.2.5), durch die Methoden als Implementierung einer Operation aufgerufen werden.
Eine Ausnahmebehandlungsroutine wird immer dann ausgeführt, wenn eine solche Ausnahme generiert wurde. Dies kann sowohl durch das Laufzeitsystem als auch den Programmierer selbst (z.B. bei Erkennen einer Fehlersituation) geschehen.

Java-Syntax:
Strukturell ist eine Ausnahme ein Java-Objekt (java.lang.exception). Dieses wird bei Bedarf entweder durch das Laufzeitsystem oder den Programmierer erzeugt.

Die grundsätzliche Möglichkeit einer Methode eine bestimmte Ausnahme zu erzeugen wird bereits in der Methodendeklaration angegeben:


public class TestClassWithException
{
   public void aMethodWithException() throws MyException
   {
      //...
   } //end aMethodWithException
} //end class TestClassWithException

In diesem Falle muss kein try- und catch-Block zum Auffangen der Exception im Programm angegeben werden.

1 Auffangen einer existierenden Exception


try
{
   //beliebige Anweisungen, die Fehler generieren können
} //end try
catch (Exception e)
{
   //Fehlerbehandlung
} //end catch
finally
{
   //weitere Anweisungen
} //end finally

Im try-Block wird diejenige Codesequenz eingebettet, die potentiell eine Ausnahmebedingung auslösen kann. Im zwingend darauf folgenden catch-Block werden die evtl. im vorangegangenen try-Block ausgelösten (auch: geworfenen -- vom englischen thrown) Exceptions aufgefangen. Jeder catch-Block kann nur genau, durch den Typ des Übergabeparametes definierte, Ausnahme auffangen.
java.lang.Exception bildet die Elternklasse aller Java-Ausnahmen. Dies bedeutet ein so deklarierter catch-Block fängt alle im try-Block auftretenden Exceptions zur Behandlung auf.
Nach allen durch try umschlossenen Anweisungen wird der finally-Block abgearbeitet. Dies erfolgt unabhängig davon, wie die vorhergehenden Anweisungen abgeschlossen wurden, sei es regulär, durch Ausnahmen oder durch den Ablauf beeinflussende Anweisungen wie return oder break. An dieser Stelle sollten Anweisungen platziert werden, die unabhängig von möglicherweise eingetretenen Fehlersituationen immer ausgeführt werden müssen. Beispielsweise die Freigabe belegter Ressourcen und Speicherbereiche.

Beispiel 14:


public class ArrayExceptionTest
{
   public static void main(String[] argv)
   {
      try
      {
         System.out.println("argv[1]=" +argv[1] );
      } //end try
      catch (Exception e)
      {
         System.out.println("exception caught: "+ e.getMessage() );
         e.printStackTrace();
      } //end catch
   } //end main()
}//end class ArrayExceptionTest

Beispiel 14 illustriert den praktisch häufigsten Fall, das Auffangen einer vordefinierten Ausnahme. Das Programm nutzt die bereits bekannte Auswertung von Kommandozeilenargumenten.
Werden weniger als zwei Aufruf-Argumente angegeben, so erzeugt das Laufzeitsystem beim Versuch die Komponente argv[1] auszugeben eine java.lang.ArrayIndexOutOfBoundsException: 1 Ausnahme.
Die beiden an jede Exception vererbten Methoden getMessage und printStackTrace liefern eine genauere Beschreibung des aufgetretenen Fehlers. Im Falle des Beispiels 14 liefert ein Aufruf mit java ArrayExceptionTest (d.h. ohne Kommandozeilenparameter):


exception caught: 1
java.lang.ArrayIndexOutOfBoundsException: 1
        at ArrayExceptionTest.main(ArrayExceptionTest.java:7)

Die Meldung (engl. message) enthält als nähere Fehlerinformation dasjenige Arrayelement, welches beim Zugriff(-sversuch) den Fehler verursacht hat.
Durch printStackTrace kann die genaue Fehlerbezeichnung, nebst Quelldatei inklusive auslösender Zeilennummer ermittelt werden.

2 Werfen von Exceptions
Dem Programmierer steht -- wie dem Laufzeitsystem -- die Möglichkeit des Werfens von existierenden oder selbstdefinierten Ausnahmen offen.

Java-Syntax:


throw new ExceptionName();

Das Schlüsselwort throw gefolgt vom new-Operator erzeugt ein neues Exception-Objekt. Die Ausnahme wird durch die Signatur ihres Konstruktors vertreten.

Hinweis: Prinzipiell kann ein Exception-Objekt auch explizit durch den new-Operator erzeugt werden. Entsprechend der Exception-Definition wird auch in diesem Falle zunächst der Konstruktor ausgeführt. Jedoch erfolgt kein Ansprung des catch-Blocks, sondern die Ausführung wird nach der Objekterzeugung fortgesetzt.

Nach einem throw-Aufruf wird der Programmablauf innerhalb der geworfenen Ausnahme fortgesetzt. Das bedeutet alle Anweisungen nach einem solchen Aufruf sind im Ablauf niemals erreichbar, der Java-Compiler meldet deshalb immer einen Statement not reached. Fehler.

3 Definition eigener Ausnahmen
Alle Ausnahmen werden als explizite Spezialisierungen der Klasse java.lang.Exception definiert.
Syntaktisch ist die Deklaration einer Exception-Klasse mit der einer "normalen" Klasse identisch.

Beispiel 15 (Quelldatei: ExceptionTest.java):


public class ExceptionTest
{
   public static void main(String[] args)
   {
      try
      {
         System.out.println("raising... ");
         throw new myException("this is a test message...");
      } //end try
      catch (myException ExceptionObj)
      {
         System.out.println("exception catched!");
         System.out.print("Stack trace:");
         ExceptionObj.printStackTrace();
         System.out.println("Message: "+ ExceptionObj.getMessage() );
      } //end catch (myException)
      finally
      {
         System.out.pritln("finally reached...");
      } //end finally
      System.out.println("...continuing");
   } //end main()
} //end class Test4
//---------------------------------------------------------
class myException extends Exception
{

   public myException(String exceptionMsg) //constructor
   {
      super(exceptionMsg);
      System.out.println("myException raised");
   } //end myException()
}//end class myException

Im Beispiel 15 wird eine anwenderdefinierte Exception (myException) als Spezialisierung der Exception-Klasse definiert. Ihr Konstruktor belegt das ererbte Meldungstext-Attribut durch expliziten Aufruf des Superklassenkonstruktors. Anschliessend wird eine Textzeile am Bildschirm ausgegeben.
In der main-Methode des Hauptprogramms wird im try-Block durch den Programmierer die definierte Ausnahme ausgelöst (geworfen). Im Konstruktoraufruf wird ein frei definierter Text zur näheren Fehlerbeschreibung übergeben.
Der zugehörige catch-Block fängt die geworfene Exception auf. Er gibt die Umgebung (stack trace) sowie den zuvor bei Initialisierung des Exception-Objekts gesetzten Fehlertext aus.
Nach Abschluss der Fehlerbehandlung des catch-Blocks wird der Programmablauf im finally-Block fortgesetzt. Danach werden alle ausserhalb der try-catch-finally-Struktur platzierten Anweisungen abgearbeitet.

Hinweis:try-Blöcke können geschachtelt werden, um lokales Exception-Handling zu implementieren.

back to top   6 Ein- und Ausgabe

 


Definition: Stream
Gerichtete Informationsverbindung zwischen einem Informationserzeuger und einer Informationssenke, welche die bereitgestellte Information konsumiert.
Mit Stream wird eine universelle Kommunikationsprimitive bezeichnet. Abhängig weder von Art und interner Struktur der verstandten Daten, noch von der Realisierung des Erzeugers oder Konsumenten.

Stream-Implementierung in der Java-Plattform:
Die gesamte Stream-bezogene Funktionalität der Java2-Plattform findet sich im Standard-API-Package java.io. Jegliche Stream-Verarbeitung basiert auf den beiden abstrakten Klassen InputStream für lesende, bzw. OutputStream für schreibende Streams. Darüberhinaus liefert die abstrakte Basisklasse auch die Standard-Ausnahme IOException aller Streams.
Diese beiden Klassen werden gemäß der spezifischen Anforderungen an den einzusetzenden Stream spezialisiert.
Quelle eines Ein- oder Ausgabestreams kann jedes beliebige verfügbare Gerät sein (z.B. Tastatur via Kommandozeile, Datei im Dateisystem oder Netzressource). Für die häufigsten Quellen und Senken sind Standardgeräte vordefiniert. Dies sind:

Generell werden Streams durch Initialisierung mit der gewünschten Eingabequelle bzw. Ausgabesenke geöffnet. Das explizite Schliessen erfolgt im Programm durch Aufruf der Methode close(), andernfalls durch das Laufzeitsystem bei Terminierung der Applikation.

5.1 Eingabestreams

Beispiel 16 stellt eine mögliche Implementierung des UNIX-Systemkomandos cat vor.
Zunächst wird eine Variable vom Typ InputStream deklariert. Der so geschaffene Eingabestrom wird mit System.in auf die Kommandozeile gesetzt. Innerhalb einer kopfgesteuerten Schleife wird zunächst in der Fortsetzungsbedingung ein einzelnes Zeichen eingelesen (durch die Methode read() auf dem selbstdefinierten Objekt inStream vom Typ InputStream). Nur wenn eine von -1 verschiedene Ganzzahl zurückgegeben wurde (d.h. der Eingabestrom nicht zu Ende ist) wird das gelesene Zeichen im Schleifenkörper an der Kommandozeile ausgegeben.

Beispiel 16:(Quellcodedatei: cat.java)


   import java.io.InputStream;
   import java.io.IOException;

   public class cat
   {
      public static void main(String[] args)
      {
         InputStream inStream;
         inStream = System.in;
         int readByte=0;

         while ( (readByte = inStream.read()) != -1)
         {
            System.out.print((char) readByte);
         } //end catch
         inStream.close();
      }//end main()
   }//end cat

Der übliche Aufruf mit java cat gibt jede durch Return bestätigte Zeichenkette am Bildschirm aus, bis die Applikation durch CTRL-C abgebrochen wird.
Durch Umlenkung des Ausgabestroms (engl. piping) kann cat zum Kopieren in Dateien eingesetzt werden (java cat > outfile). Wird auch der Eingabestrom umgelenkt, so kann cat die Funktion eines Kopierbefehls wahrnehmen (java cat < infile > outfile).

Durch die identische Mimik, mit Ausnahme der Belegung der InputStream-Variablen kann auch das Lesen aus einer Datei realisiert werden. Dies ist in Beispiel 17 ausschnittsweise dargestellt.

Beispiel 17:


   InputStream anotherStream;
   anotherStream = new FileInputStream("c:\myTestFile");
   int content;

   content = anotherStream.read();
   

Die möglichen Ausrichtungen eines InputStreams ergeben sich durch die zulässigen Typausprägungen die eine Variable der Klasse InputStream annehmen kann. Technisch gesehen bedeutet dies: Eine InputStream-Variable kann gemäß der Typeinschränkung nur Objekte vom Typ InputStream oder eines Subtypen dieser Klasse beherbergen.

Subklassen der Klasse java.io.InputStream

Durch die Klasse InputStream wird die Möglichkeit eines Verbindungsaufbaus, mit dem Ziel Daten in eine Applikation einzulesen, aufgebaut. Die Struktur dieser Daten ist durch den Stream nicht näher definiert.
Die zugehörige Leseoperation wird durch die Subklassen der abstrakten Klasse Reader (ebenfalls im Paket java.io) zur Verfügung gestellt.

6.2 Ausgabestreams

Analog der Eingabeverarbeitung ist die Definition und Behandlung der Ausgabestreams -- als Subklassen von OutputStream angelegt.
Beispiel 18 zeigt die Nutzung eines In- und eines Output-Streams zur Realiserung des UNIX-Kommandos cp

Subklassen der Klasse java.io.OutputStream

Beispiel 18(Quellcodedatei: cp.java):


   import java.io.InputStream;
   import java.io.FileInputStream;
   import java.io.OutputStream;
   import java.io.FileOutputStream;
   import java.io.IOException;
   import java.lang.Exception;

   public class cp
   {
      public static void main(String[] argv) throws IOException
      {
         if (argv.length == 2)
         {
            InputStream inStream=null;
            OutputStream outStream=null;
            int processedCharacter=0;

            try
            {
               inStream = new FileInputStream( argv[0] );
               outStream = new FileOutputStream( argv[1] ); //create new file
               while ( (processedCharacter = inStream.read()) != -1)
               {
                  //while not at end of file
                  outStream.write( processedCharacter );
               } //end while
            } //end if
            catch (Exception e)
            {
               if(e instanceof IOException)
               {
                  System.out.println("IOException caught");
               }//endif
               else
               {
                  System.out.println("unknown exception caught");
                  e.printStackTrace();
               } //end else
               System.out.println("exception's message: "+e.getMessage() );
            } //end catch
         } //endif
         else
         {
            System.out.println("wrong number of parameters");
         } //end else
      } //end main()
   } //end class cp
   

Erläuterungen zu Beispiel 18:
Zunächst werden die notwendigen Variablen (ein Ein- und ein Ausgabestream sowie die int-Variable zur Zwischenspeicherung des gelesenen Zeichens) deklariert.
Die Quelldatei und Zieldatei werden dabei als Kommandozeilenparameter übergeben.
Verläuft das Öffenen der Eingabe- und Ausgabedatei fehlerfrei wird in einer Schleife bis zum Dateiende die Quelldatei zeichenweise in die angegebene Zieldatei kopiert.
Anmerkung zur Realisierung des Exception-Handlings:
Die Applikation verwendet sowohl einen catch-Block zum Auffangen ausgelöster Exceptions, als auch die IOException in der Methodendeklaration der main-Methode; d.h. diese Exception wird (sofern sie durch die read-Anweisung innerhalb der Fortsetzungsbedingung der while-Schleife geworfen wird) nicht durch den catch-Block aufgefangen und behandelt, da sich die while-Anweisung nicht im zugehörigen try-Block befindet.




separator line
Service provided by Mario Jeckle
Generated: 2004-06-07T12:31:56+01:00
Feedback Feedback       SiteMap SiteMap
This page's original location This page's original location: http://www.jeckle.de/vorlesung/sei/kII.html
RDF metadata describing this page RDF description for this page