Java / Kontrollfluss, Exceptions, Thread - Fachbereich 4: HTW Berlin
Java / Kontrollfluss, Exceptions, Thread - Fachbereich 4: HTW Berlin
Java / Kontrollfluss, Exceptions, Thread - Fachbereich 4: HTW Berlin
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
Lecture Notes:<br />
<strong>Java</strong> / <strong>Kontrollfluss</strong>, <strong>Exceptions</strong>, <strong>Thread</strong><br />
(Version 1.0, vom 30.04.2010, Autor: Prof. Schwotzer)<br />
<strong>Java</strong> ist eine imperative Programmiersprache. Die einzelnen Statements eines Programmes<br />
beschreiben, was zu tun ist. Die Statements werden nacheinander abgearbeitet. Man nennt<br />
die Abfolge der Statements den <strong>Kontrollfluss</strong> (control flow). Der <strong>Kontrollfluss</strong> kann in<br />
Schleifen und Verzweigungen gebracht werden. Dazu dienen die bereits aus C bekannten<br />
Steuerelemente while, do-while, for und if-then-else. Diese Steuerelemente führen dazu, dass<br />
der Steuerfluss Zeilen überspringt bzw. wieder in bereits abgearbeitete Statements<br />
zurückspringt. Diese Elemente sind aus C hinlänglich bekannt und sollen hier nicht<br />
wiederholt werden.<br />
In <strong>Java</strong> spricht man auch davon, dass sich ein Asuführungsfaden (thread) durch das<br />
Programm zieht. Der erste Ausführungsfaden beginnt mit dem ersten Statement der<br />
gestarteten Main-Methode. Dieser <strong>Thread</strong> endet, wenn die Main-Methode beendet ist.<br />
Visualisieren Sie sich das einmal an einem beliebigem Programm.<br />
In <strong>Java</strong> kann man diesen <strong>Thread</strong> manipulieren, d.h. man kann ihn z.B. verzögern. Das<br />
geschieht durch folgendes Kommando:<br />
<strong>Thread</strong>.sleep(100);<br />
Das obige Statement sorgt dafür, dass der Ausführungsthread 100 Millisekunden pausiert und<br />
dann weiterläuft. Das Konzept des <strong>Thread</strong> mag auf Anhieb etwas eigenartig erscheinen.<br />
Versuchen Sie es zu verinnerlichen, auch wenn es etwas schwer vorstellbar wirken sollte.<br />
Konsequent ist es allemal:<br />
Der Ausführungsfaden, der <strong>Kontrollfluss</strong> ist ein Ding. Es ist eine Entität, die eine wesentliche<br />
Rolle in einem OO-System spielt. Konsequenterweise existiert ein Objekt, dass diese<br />
<strong>Kontrollfluss</strong> repräsentiert. Und dieses Objekt kann man manipulieren. Lesen Sie sich einmal<br />
weitere Methoden der Klasse <strong>Thread</strong> durch. Wie werden im Abschnitt „Multithreading“<br />
darauf zurückkommen.<br />
Ausnahmen<br />
Wenn Sie eine Methode aufrufen, können grob drei Dinge passieren:<br />
• Die Methode wird erfolgreich ausgeführt.<br />
• Bei der Ausführung der Methode entstand ein Fehler. Das kann verschiedene<br />
Ursachen haben<br />
◦ Sie haben sich beim Aufruf nicht an den Contract gehalten, d.h. Es wurden falsche<br />
Eingabeparameter übergeben,<br />
◦ Während der Ausführung zeigte sich, trotz korrekter Eingabewerte, dass die<br />
Methode nicht korrekt ausgeführt werden kann. Sie kann kein Ergebnis liefern.<br />
• Während der Ausführung entstand ein Fehler, der nicht im Programm, sondern zur<br />
Laufzeit des Programmes auftrat. Vielleicht ist die Datenbank gerade ausgefallen, auf
die Sie zugreifen. Das File, das just geöffnet wurde, lässt sich aus unklaren Gründen<br />
nicht mehr schreiben, vielleicht ist der USB-Stick entfernt worden, …<br />
In C werden diese Fälle lediglich durch den Rückgabeparameter entschieden. Ein guter<br />
Aufruf in C sieht so aus:<br />
int retval = f();<br />
if(retval == ERROR) {<br />
/* do something */<br />
} else {<br />
/* there is a result – do something useful */<br />
}<br />
Im Prinzip muss nach jedem Funktionsaufruf geprüft werden, ob die Funktion überhaupt<br />
erfolgreich ausgeführt werden konnte. Das ist a) nertötend und b) unhandlich, weshalb viele<br />
C-Programme darauf verzichten, was sie noch fehleranfälliger machen.<br />
In <strong>Java</strong> hat man die unterschiedlichen Aspekte eines Rückgabewertes eine Methode getrennt,<br />
indem das Konzept der Exception eingeführt wurde. Folgendes Codebeispiel soll die Nutzung<br />
einer Exception illustrieren:<br />
public void divide(int a, int b) throws Exception {<br />
if(b == 0) {<br />
Exception e = new Exception(“b darf nicht 0 sein“);<br />
throw e;<br />
}<br />
}<br />
return a / b;<br />
Exception ist eine Klasse. Von ihr können Instanzen erzeugt werden. Das erfolgt mittels<br />
new(). Ein Exception-Objekt repräsentiert einen konkreten Fehlerfall. In obigen Beispiel wird<br />
ein Fehlerfall erkannt, wenn der zweiten Parameter 0 ist. In dem Fall wird ein Objekt erzeugt,<br />
dass dieses Fehlerfall dokumentiert. Danach wird dieser Fehler mittels throw() geworfen. An<br />
dieser Stelle wird der <strong>Kontrollfluss</strong> unterbrochen und an der Stelle fortgesetzt, an der die<br />
Methode aufgerufen wurde.<br />
Beachten Sie, dass Methoden deklarieren müssen, wenn das Werfen einer Exception möglich<br />
ist. Das erfolgt mittels des Statement throws Exception.<br />
Ein aufrufender Prozess kann auf zwei Arten mit möglichen <strong>Exceptions</strong> umgehen: Er kann sie<br />
behandeln oder ebenfalls werfen. Beispiel:<br />
...<br />
int a = ?;<br />
int b = ?;<br />
int result;<br />
try {<br />
result = this.divide(a, b);<br />
}<br />
catch(Exception e) {<br />
System.err.println(“etwas wirklich dummes passierte hier: “ + e.getMessage());<br />
}<br />
finally {<br />
// was immer passierte, finally wird immer ausgeführt<br />
}
Wenn mögliche <strong>Exceptions</strong> bearbeitet werden sollen, so müssen sie in einem try-catch-Block<br />
stehen, siehe oben. Try bedeutet Versuchen. Es wird also versucht, Methoden auszuführen. In<br />
dem Moment, wenn eine Methode mit einer Exception abgebrochen wird, wird auch der<br />
<strong>Kontrollfluss</strong> innerhalb des try-Block abgebrochen.<br />
Die Metapher bei Exception ist diese: Ein Exception-Objekt repräsentiert ein Problem, einen<br />
Fehler, der zum Abbruch der Methode führte. Dieses Objekt wird aus der abgebrochenen<br />
Methode herausgeworfen (throw). Wird eine Exception-Objekt nicht gefangen, so bricht es<br />
auch die aufrufenden Methode ab und so weiter, bis es bei einer ersten main()-Methode<br />
landet.<br />
Exception-Objekte können aber gefangen werden (catch). Nun ist die Abbruchkaskade<br />
unterbrochen. Die Exception wird behandelt. Die Art der Behandlung ist frei. Nicht selten,<br />
wird das Problem lediglich dokumentiert, ein Default-Wert wird gesetzt und die Methode<br />
läuft weiter. Manchmal wird das Problem dokumentiert und die Exception wird weiter<br />
geworfen. Es steht Ihnen frei, mit <strong>Exceptions</strong> umzugehen.<br />
Es gibt einen dritten Block, der finally-Block. Er wird eher selten genutzt. Dieser Code wird in<br />
jedem Fall abgearbeitet, egal ob eine Exception geworfen wurde oder nicht.<br />
Ein Beispiel im Semiar wird das Vorgehen vertiefen. Lesen Sie auch in einem <strong>Java</strong>-Buch das<br />
Konzept der <strong>Exceptions</strong> nach.<br />
Klassen von <strong>Exceptions</strong><br />
Es gibt eine Klassenhierarchie von Ausnahmen. Die Superklasse ist Throwable (Werbare).<br />
Davon abgeleitet sind RuntimeException und Exception. Der Unterschied ist synaktischer<br />
und semantischer Natur. Runtime<strong>Exceptions</strong> sind Fehler, die zur Laufzeit entstehen können<br />
und auf deren Entstehung des Programm keinen Einfluss hat: Ausfall einer Datenbank,<br />
Zusammenbruch des Netzwerkes, Fehler in externen Datenträgern sind Beispiele von<br />
Laufzeitfehlern. Laufzeitfehler können jederzeit entstehen und jederzeit abgefangen werden.<br />
Runtime<strong>Exceptions</strong> müssen aber nicht gefangen werden. Exception ist die Basisklasse aller<br />
Ausnahmen, die im Kontext der Anwendung entstehen können. Sie zeigen Fehler an, die sich<br />
aus der eigenen Anwendung, dem eigenen Programm, z.B. durch fehlerhalftes Verwenden von<br />
Parameter etc. ergeben. <strong>Exceptions</strong> werden in der Methode deklariert und müssen gefangen<br />
oder geworfen werden.<br />
Einige Regeln<br />
• Ein klassischer Anfängerfehler besteht in der Annahme, dass das Werfen von<br />
<strong>Exceptions</strong> eine Art Unfähigkeit andeutet. Hier stimmt aber das Gegenteil. Profis<br />
dokumentieren Fehler, Anfänger tendieren dazu, dass Fehler als etwas schlechtes zu<br />
erkennen und meinen dass deren Existenz irgendwie und möglichst schnell behoben<br />
werden muss. Das ist in realen Systemen in der Regel nicht möglich. Wie wollen Sie<br />
bspw. in einer Methode arbeiten, die Zahlen addiert, wenn plötzlich eine Datenbank<br />
ausfällt. Sehr häufig gibt es keine eindeutig und klaren Möglichkeiten, mit einem<br />
Problem umzugehen. Das Werfen einer Exception ist daher der empfohlene Weg,<br />
Probleme zu dokumentieren und zu publizieren. Arbeiten Sie professionell und<br />
werfen Sie <strong>Exceptions</strong>!<br />
• Es ist guter Stil, wenn die try-Blöcke klein sind. Der Code mag eigenartig aussehen,
wenn nur wenige Methoden geklammert sind und ständig <strong>Exceptions</strong> abgefangen<br />
werden. Ein Anfängerfehler besteht darin, lange try-Blöcke zu schreiben, die aber<br />
nicht zusammen gehören. Statement gehören dann in einen try-Block, wenn nach<br />
dem Abbruch der Abarbeitung keine Statement, aber auch wirklich kein weiteres<br />
Statement sinnvoll ausgeführt werden kann. Dann kann der gesamte Block<br />
unterbrochen werden und die Ausnahmebehandlung kann starten.<br />
Wenn aber die Abarbeitung mittels des Setzens eine Defaults oder ähnlichen<br />
weitergehen kann, so ist der try-Block zu trennen. Das werden wir üben.<br />
• Definieren Sie eigene Exception-Klassen. Wenn Sie eine eigene Anwendung haben, so<br />
definieren Sie sich eine eigenen Exception-Klasse<br />
class MyException extends Exception {}<br />
Auf diese Weise können Sie überall erkennen, ob die Exception aus Ihrem System<br />
kommt oder von anderen Stellen. Wenn Sie unterschiedliche Klassen von Ausnahmen<br />
haben, so definieren Sie mehrere Klassen, z.B. WrongParameterMyException,<br />
NoUserReplyMyException, die alle von Ihrer Basisklasse ableiten. Dadurch wird<br />
solcher Code möglich:<br />
...<br />
try {...}<br />
catch(WrongParameterMyException wpme) { ... }<br />
catch(NoUserReplyMyException nurme) { ... }<br />
catch(MyException me) { ... }<br />
catch(Exception e) { ... }<br />
Durch so ein Vorgehen, können Sie zunächst sehr spezielle und dann immer<br />
konkretere Ausnahmen auffangen. Vieles catch-Blöcke zeugen in der Regel von einem<br />
professionellen Vorgehen. Hier wurden mögliche Fehler und deren Behandlung<br />
ausführlich beachtet.<br />
• Fangen Sie nur Runtime<strong>Exceptions</strong>, wenn Sie genau wissen, was Sie dagegen tun<br />
können. Es ist manchmal deutlich besser, wenn ein Programm kontrolliert terminiert,<br />
als wenn es irgendwie weiterläuft. Beispiel: Wenn das Netzwerk zusammenbricht, so<br />
können Sie in Ihrem <strong>Java</strong>-Programm durchaus <strong>Exceptions</strong> dazu fangen und das<br />
Programm am laufen halten. Es wird nur keinen sinnvollen Beitrag mehr liefern.<br />
Beenden Sie es besser. Dokumentieren Sie ggf. den Fehler, aber machen Sie Schluss.