.: java - programmiergrundlagen :.
 
abyter.de \\ workshops

< Fortsetzung von letzter Seite

Wiederholungsstrukturen
Des öfteren ist es erforderlich, dass die gleichen Anweisung mehr als einmal ausgeführt werden müssen. Man könnte nun die Anweisungen manuell durch Mehrfacheingabe wiederholen. Das ist jedoch sehr umständlich und sehr oft nicht zweckmäßig. Was sollte man z.B. machen, wenn man solange etwas wiederholen möchte, bis der Benutzer eine bestimmte Taste drückt.
Für solche und andere Fälle gibt es Wiederholungsstrukturen. Diese wiederholen eingeschlossene Anweisungen abhängig von einer Bedingung (oder eben nicht).

Schleife mit Anfangsabfrage
Wie immer gibt es verschiedene Methoden Anweisungen zu wiederholen. Die erste ist die mit Anfangsabfrage, d.h. die Bedingung wird immer VOR der Ausführung der Wiederholung geprüft. Diese Schleifenart wird auch kopfgesteuerte Schleife genannt, da die Anweisung im Schleifenkopf steht. Neben dem Schleifenkopf gibt es noch den Schleifenkörper, in dem die Anweisung(en) stehen.
Bei dieser Schleifenart kann es vorkommen, dass die Schleife nie ausgeführt wird, da die Schleifenbedingung von Anfang an nicht erfüllt wird. Solange die Schleifenbedingung aber von Anfang an wahr ist, solange wird die Schleife durchlaufen.
In der obigen Definition habe ich die Worte solange und wahr kursiv geschrieben. Diese Worte entsprechen nämlich ungefähr dem Java-Syntax für eine Schleife mit Anfangsabfrage. Die exakte Syntax sieht so aus:
while(Bedingung) Anweisung;

Zu deutsch also: Solange (Bedingung = wahr) Anweisung. Wie schon bei den vorhergehenden Kontrollstrukturen ist auch bei den Schleifen die mehrzeilige Anweisung in geschweifte Klammern zu schreiben.
Bei allen Schleifen besteht die Gefahr einer Endlosschleife. Bei der Endlosschleife ist die Bedingung falsch gewählt und zwar so, dass die Bedingung immer wahr ist und somit die Schleife unendlich oft wiederholt wird. Da der Benutzer keinen Einfluss mehr auf das Programm hat, dass immer weiter die gleichen Anweisungen ausführt, gilt das Programm als abgestürzt. Im schlimmsten Fall muss der Computer neugestartet werden. Aus diesem Grund ist es unbedingt erforderlich, dass die Schleifenbedingung genau überprüft wird, damit es nicht zu Endlosschleifen kommt.
Noch schlimmer ist nur noch, wenn man auch noch den Fehler begangen hat, ein Semikolon hinter while (Bedingung) zu setzen, wenn danach noch in geschweiften Klammern die Anweisungen stehen. Dann nämlich ist für den Benutzer nicht ersichtlich, was der Computer gerade macht. Da er immer unentwegt nichts macht, kommt es zu einem Hänger, der die gleichen Auswirkungen wie die Endlosschleife hat.

Schleife mit Endabfrage
Die zweite Möglichkeit der Wiederholung von Anweisungen ist die Schleife mit Endabfrage. Die Schleife wird auch fußgesteuerte Schleife genannt, da die Bedingung im Schleifenfuß steht. Die Schleife mit Endabfrage wird IMMER mindestens einmal durchlaufen. Die Schleife wird solange wiederholt, solange die Bedingung wahr ist. Somit ergibt sich folgende Java-Syntax:

do
{
Anweisung;
}
while (Bedingung);

Ansonsten gelten die gleichen Grundlagen wie bei der Schleife mit Anfangsabfrage.

Zählschleife
Die Zählschleife ist eine Schleife, bei der von Anfang an feststeht, wie viele Wiederholungen ausgeführt werden. Dabei wird die Anzahl der Wiederholungen von der Laufvariable (dem Zähler) gesteuert. Die Laufvariable erhöht sich bei jeder Ausführung um eine festgelegte Größe. Die Schleife wird solange wiederholt, bis der vorher festgelegte Endwert von der Laufvariable erreicht worden ist.
Die Syntax sieht etwas komplizierter aus, ist aber halb so schlimm:
for (Laufvariable=Anfangswert; Laufvariable<=Endwert; Laufvariable++) Anweisung;

Die Anweisung lässt sich in fünf Teile zerteilen. Das erste ist das Schlüsselwort for. In der Klammer stehen die nächsten drei jeweils getrennt durch ein Semikolon.
Erst kommt die Initialisierung der Laufvariable (Laufvariable=Anfangswert;), der Laufvariable wird der Anfangswert zugewiesen. Es folgt die Bedingung (Laufvariable<=Endwert;), bei der selbstverständlich die Laufvariable kleiner oder gleich dem Endwert sein muss. Ist das nicht mehr der Fall, wird die Schleife verlassen.
Die letzte in der Klammer stehende Anweisung (Laufvariable++) zeigt einen der Ursprünge von Java. Es handelt sich um einen sogenannten Postinkrementoperator, den der gebildete Programmierer aus C++ (gibt C++ seinen Namen) kennt. Dieser Postinkrementoperator tut nichts anderes, als die Laufvariable um eins zu erhöhen.
Nach der Klammer folgt dann selbstverständlich die Anweisung.

Arrays
Bis jetzt bestanden Variablen nur aus einem Wert, einer Zahl oder einem String. Arrays aber können eine beliebige Anzahl von Werten des gleichen Typs beinhalten. Bei der Erzeugung eines Arrays wird auch die Größe des Arrays festgelegt, d.h. wie viele Elemente gleichen Typs im Array gespeichert werden können. Das Array selbst besteht aus Feldern, diese sind mit dem sogenannten Index durchnummeriert und werden über den Index angesprochen. Der Index ist also die Adresse der Elemente im Array. Der Index beginnt mit null, und endet mit n-1. Dass heißt wenn man ein Array von der Größe sechs erzeugt, dann beginnt es mit null und endet mit fünf.
Das Array selbst wird, wie eine Variable auch, über seinen Namen angesprochen, da es aber viele Elemente (n + 1) des gleichen Typs enthält, wird zusätzlich noch der Index mit angegeben. Die Namensgebung des Arrays unterliegt den selben Beschränkungen wie eine Variable.
So jetzt wollen wir aber in die Java-Syntax einsteigen. So wird allgemein ein Array erzeugt:
Datentyp arrayName[];

Also z.B. int noten[];

So deklariert man zwar das Array, jedoch hat man es noch nicht dimensioniert. Das geht allgemein so:
arrayName = new Datentyp[Anzahl der Elemente];

Also z.B. noten = new int[10];

Wenn man es also ausführlich machen will sieht eine komplette Deklaration so aus:
int noten[];
noten = new int[10];

Man kann sich jedoch auch weniger Arbeit machen und das ganze aufeinmal machen:
int noten[] = new noten[10];

Mehrdimensionale Arrays
Es ist außerdem möglich, sogenannte mehrdimensionale Arrays mit Java zu erzeugen. Die Syntax ist der eines eindimensionalen Arrays sehr ähnlich. Die Syntax lautet:
Datentyp arrayName[] [] = new Datentyp [wert1] [wert2];

Also z.B. int noten[] [] = new int [5] [7];

Der Zugriff auf ein mehrdimensionales Array erfolgt genauso wie bei dem eindimensionalen Array mit dem Arraybezeichner und den Indices.

Exception Handling
In jedem noch so guten Programm kann auch mal ein Fehler auftreten (z.B. von außen: Papier von Drucker leer). Der Fehler ist nur noch halb so schlimm wenn man dafür eine gute und wirksame Fehlerbehandlung (Exception Handling) vorsieht.
In Java ist die Fehlerbehandlung nicht besonders schwierig. Fehleranfällige Programmteile werden von den Schlüsselwörtern try und catch umgeben. Das sieht ungefähr so aus:

try
{
Anweisungen
}
catch()
{
Anweisungen bei Fehlern
}

Wie zu sehen ist steht im Block nach try der Programmcode der auf Fehler zu überwachen ist und hinter catch folgt der Programmteil, der dann ausgeführt wird, wenn der Fehler abgefangen wurde. Es sind auch mehrere catch-Blöcke möglich. Im catch-Block muss die Art der Exception angegeben werden. Möglich Exception-Arten sind: catch(StringIndexOutOfBoundsException e) für Fehler die beim Zugriff auf Strings entstehen; catch(NumberFormatException e) für Fehler die beim Umwandeln in einen anderen numerischen Datentyp entstehen oder catch(Exception e) für alle anderen Fehler. Die mögliche Art der Exception lässt sich herausfinden, indem man nach den eingesetzten Anweisungen (z.B. Integer.parseInt) in der JDK-Dokumentation sucht. In der Beschreibung befindet sich auch die mögliche Exception Art. Nach der Exception-Art muss noch eine Variable stehen über die dann Informationen über den Fehler ausgegeben werden können (z.B. e.toString()).
Werden mehrere catch-Blöcke eingesetzt, so sollte immer der allgemeine catch-Fall (catch(Exception e)) zu letzt stehen, da alle catch-Fälle der Reihe nach aufgerufen werden bis der erste auf den Fehler zutrifft. Steht der allgemeine nicht am Ende, so kann man keine spezifische Fehlerbehandlung vornehmen.
Sollen Anweisungen im Fehlerfall nochmals wiederholt werden (z.B. Eingabe von Zahlen), so muss man diese in eine Endlosschleife packen, die könnte so aussehen:

while(true)
{
System.out("Ganze Zahl: ");
try
{
i = Integer.parseInt(in.readLine());
break;
}
catch (IOException e)
{
System.out.println("IOException\n"+e);
System.exit(0);
}
catch (NumberFormatException e)
{
System.out.println("NumberFormatException\n"+e);
}
}

Mit System.exit(0) wird das Programm abgebrochen.
Das von dem ImputStreamReader bekannte throws IOException nach der main-Deklaration bewirkt, dass Fehler an die nächst höhere Ebene weitergegeben werden. So braucht man pro Klasse nur ein Exception Handling zu schreiben. Wird in der nächsthöheren Ebene ebenfalls kein Exception Handling durchgeführt, dann kommt es zu einer Laufzeitfehlermeldung durch den Interpreter.

Auswerfen von Exceptions
Bis jetzt sind Fehler immer nur vom Laufzeitsystem "geworfen" worden, man kann jedoch selbst Exceptions erzeugen! Wenn man z.B. nur Zahlen kleiner Zehn haben möchte kann man eine Exception erzeugen, wenn die Zahl größer als zehn ist. Dies geht mit dem Schlüsselwort throw. Eine Anweisung mit throw könnte so aussehen:
throw new SecurityException(zahl +" ist zu groß!");

Die Aufrufe müssen dann natürlich im try-Block stehen!

Eigene Exception-Klassen entwerfen
Man kann auch eigene Exception-Klassen definieren. Diese erben dann alle von der Klasse Exception. Eine eigene Exception-Klasse könnte Exceptions auslösen wenn eine negative Zahl eingegeben wird. Eine solche Exception könnte so aussehen:

public class NegativeIntegers extends Exception
{
public NegativeIntegers()
{
super();
}
public NegativeIntegers(int i)
{
super ("Die Zahl " + i + " ist negativ! Das ist nicht erlaubt!");
}
}

Die selbstentworfene Exception wird nun ganz normal in der Hauptklasse verwendet. Es wird dabei mit throws in der Methoden-Definition gearbeitet. Und eine Exception nach einer Überprüfung geworfen.

Lesen und schreiben von externen Dateien
Bis jetzt bestanden unsere Programme immer aus einer Eingabe von Hand oder war bereits im Quelltext gemacht worden, das Ganze wurde dann auf dem Bildschirm ausgegeben. Es ist jedoch mit Java auch möglich externe Dateien einzulesen, bzw. in diese Ausgaben zu machen.
In der Eingabe von Hand ist schon ein Schlüsselwort eingebaut, das bei der Ein- und Ausgabe in Dateien mit Java eine große Rolle spielt den 'Stream' (z.B. in InputStreamReader). Der Stream ist ein Datenfluss von der Eingabe hin zur Ausgabe, spielt also bei beidem eine Rolle.
In Java gibt es im Paket java.io.* eine Vielzahl von Streams (über 30); ich möchte vorerst nur den FileInputStream, den FileOutputStream, den FileWriter und den BufferedReader behandeln.

FileInputStream
Für einfache Dateieingaben benutzt man die Klasse FileInputStream. Man bindet mit dieser Klasse eine Datei an einen Datenstrom.
Allgemein sieht die FileInputStream-Anweisung so aus: FileInputStream(String)
Damit wird ein FileInputStream mit einen gegebenen Dateinamen erstellt. Im Quelltext könnte das dann so eingesetzt werden:

[...]
FileInputStream in = new FileInputStream("Readme.txt");
int zeichen =0;
while ((zeichen=in.read())!=-1)
System.out.print((char)zeichen);
in.close();
[...]

Hier sieht man warum der FileInputStream nur für einfache Dateieingaben verwendet wird. Er liest Byte für Byte aus der Datei aus und liefert int-Werte (Die dem ASCII-Key entsprechen) zurück. Da alles Byte für Byte geschieht, muss man eine Schleife erstellen, die den Vorgang solange wiederholt bis die Datei zu Ende ist (ASCII-Key -1 bedeutet Dateiende). Für die Ausgabe müssen die int-Werte mittels Typecast ((char)zeichen) in Buchstaben umgewandelt werden.
Als Exceptions können FileNotFoundException und IOException auftreten. Ersteres ist als Datei nicht vorhanden und letzteres als Lesefehler zu interpretieren.

FileOutputStream
Der FileOutputStream ist das Gegenstück zum FileInputStream. Auch er ist nur für einfache Dateiausgaben geeignet.
Allgemein wird so ein FileOutputStream aus einem gegebenen Dateinamen erzeugt: FileOutputSteam(String);
Im Quelltext könnte man ihn z.B. so einsetzen:

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
FileOutputStream fileOut = new FileOutputStream("line.txt");
System.out.println("Text eingeben:");
String text = in.readLine();
fileOut.write(text.getBytes(), 0, text length());
fileOut.close();

Hierfür musste ich etwas ausholen, zuerst muss natürlich etwas eingegeben werden, dies geschieht über den BufferedReader. Dieser Text wird in einer String-Variable gespeichert und mit der write-Methode des FileOutputStream-Objektes in eine Datei geschrieben.
Wie im Quelltextschnipsel zu sehen ist, sind Parameter zwingend erforderlich! Die write-Methode ist also folgendermaßen aufgebaut: write(Daten, Anfang, Länge)
Daten sind ein Array, jedoch kein String-Array, sondern ein Byte-Array, aus diesem Grund werden die Daten mittels .getBytes() ins Byte-Format konvertiert; der Anfang ist bei 0 wenn die Datei noch leer ist und die Länge wird mittels .length() bestimmt.

FileWriter
Wesentlich einfacher geht das Schreiben in Dateien aber mit dem FileWriter. Er ermöglicht das sofortige Schreiben in Dateien. So sieht die Anweisung in Aktion aus:

FileWriter fw = new FileWriter("fileWriter.txt");
fw.write("Testzeile in Textdatei");
fw.close;

Mit den hier gezeigten Einstellungen wird eine Datei erzeugt, besteht diese schon, so wird sie gelöscht. Möchte man, dass die Textzeilen hinten angehängt werden, dann muss man einen zweiten Parameter setzen, das sieht dann so aus:

FileWriter fw = new FileWriter("fileWriter.txt", true)

Es reicht also schon ein true anzuhängen und schon ist auch dieses Problem gelöst. Es müssen nur IOExceptions abgefangen werden.

BufferedReader / FileReader
Der BufferedReader ist ein alter Bekannter aus der Eingabe über Tastatur. Er ist jedoch so universal einsetzbar, dass er auch aus Dateien lesen kann. Wenn wir uns nochmals an die BufferedReader-Deklaration erinnern wollen:

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

So nun vergleich wir das mit dieser Funktion die für das Einlesen aus Dateien zuständig ist:
BufferedReader in = new BufferedReader(new FileReader("Textdatei.txt"));

Wir erkennen, dass sich die zwei Anweisungen nur durch den Parameter unterscheiden. Beim Einlesen von Textdateien ist dieser der FileReader.
Auch der Einsatz des BufferedReaders hat Vorteile gegenüber dem Einsatz des FileInputStreams. Er kann Zeilenweise einlesen! Das macht ihn natürlich sehr viel schneller.
So könnte man ihn im Quelltext verwenden:

[...]
BufferedReader in = new BufferedReader(new FileReader("Textdatei.txt"));
String str;
while((str=in.readLine())!=null)
System.out.println(str);
in.close;
[...]

Als Exceptions können FileNotFoundException und IOException auftreten

Abschluss
Damit sind die Grundlagen der Programmierung mit Java abgeschlossen, ich empfehle jedoch für anspruchsvolleres Programmieren auch die Workshops zu Klassen und der Grafikprogrammierung mit dem AWT

Related Workshops: Allgemeines \\ Java Einführung \\ Arbeit mit dem JCreator \\ Programmieren mit Java \\ Klassen-Grundlagen \\ Grafische Programmierung \\ Datenbankprogrammierung
Related Files: JCreator LE

Workshop als PDF

<Zurück

© by www.abyter.de (Martin Monshausen)