Datenstrukturen und Algorithmen: Übungsblatt 4
Aufgabe 1:
Auf der Homepage der Veranstaltung finden Sie ein kleines aber nicht ganz korrektes Java-Programm, das zum Kopieren einer Datei gedacht ist. Es soll (von der Kommandozeile aus) wie folgt aufgerufen werden können:
> java FileCopy quelldatei zieldatei
Wenn Sie versuchen, dieses Programm zu übersetzen, meldet der Java-Compiler Fehlermeldungen, z. B.:
FileCopy.java:7: unreported exception java.io.FileNotFoundException; must be caught or declared to be thrown FileInputStream fis = new FileInputStream(args[0]);
(a) Informieren Sie sich zunächst mit Hilfe der Java-API-Dokumentation, welche Exceptions von den Anweisungen in der main-Methode des Programms ausgel¨ost werden könnten. Angenommen, Sie möchten sich um mögliche Exceptions im Programm nicht weiter kümmern und einfach nur mit möglichst wenigen Änderungen des Quelltextes das Programm übersetzen und ausführen können. Was müssen Sie tun? Oder genauer: Was fehlt im Quelltext? Es ist nur eine Kleinigkeit. Passen Sie das Programm entsprechend an!
(b) Wenn Sie Teilaufgabe (a) richtig gelöst haben, haben Sie zwar ein Programm, mit dem Sie Dateien kopieren können, es kommt aber zu unschönen Programmabbrüchen, wenn ein Benutzer Fehler beim Programmaufruf macht. So würde z.B. der Versuch, eine nicht existierende Datei zu kopieren, zu einem Programmabbruch mit der folgenden Ausgabe führen:
> java FileCopy gibtsNicht irgendwohin
Exception in thread “main” java.io.FileNotFoundException:
gibtsNicht (No such file or directory)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:106)
at java.io.FileInputStream.<init>(FileInputStream.java:66)
at FileCopy.main(FileCopy.java:8)
Implementieren Sie einen Exception-Handler, der alle möglichen geprüften Exceptions innerhalb von FileCopy behandelt. Achten Sie auch darauf, dass keine ArrayIndexOutOfBoundsException (dies ist keine geprüfte Exception) mehr auftritt.
a) Die Deklaration der Main Methode muss um das Schlüsselwort “throws Exception” erweitert werden.
Der Methodenkopf sieht nun also so aus:
public static void main(String[] args) throws Exception
Expection ist die Basisklasse (Superklasse) jeder Exception, daher wird damit jede Art von Exception abgedeckt.
b)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(args[0]);
fos = new FileOutputStream(args[1]);
int c;
c = fis.read();
while (c != -1) {
fos.write(c);
c = fis.read();
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("FEHLER! Fehler beim lesen oder schreiben der Dateien: Zu wenig Parameter");
} catch (FileNotFoundException e) {
System.out.println("FEHLER! Fehler beim lesen oder schreiben der Dateien: " + e.getLocalizedMessage());
} catch (IOException e) {
System.out.println("FEHLER! Fehler beim lesen oder schreiben der Dateien: " + e.getLocalizedMessage());
}
finally{
// Versuche die Streams wieder zu schließen, auch im Fehlerfall
try {
fis.close();
fos.close();
} catch (IOException e) {
}
}
}
} |
(a) Es muss im Methdenkopf angegeben werden, dass eine IOException auftreten kann und weiter gereicht wird. Das geschieht durch das Schlüsselwort “throws” im Methodenkopf. Die Klasse sieht dann folgendermaßen aus:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(args[0]);
FileOutputStream fos = new FileOutputStream(args[1]);
int c;
c = fis.read();
while ( c != -1 ) {
fos.write(c);
c = fis.read();
}
fis.close();
fos.close();
}
} |
(b)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) throws IOException {
try {
FileInputStream fis = new FileInputStream(args[0]);
FileOutputStream fos = new FileOutputStream(args[1]);
int c;
c = fis.read();
while ( c != -1 ) {
fos.write(c);
c = fis.read();
}
fis.close();
fos.close();
}
catch (ArrayIndexOutOfBoundsException e) {
if (Integer.parseInt(e.getMessage()) == 0) {
System.out.println("Fehler: Keine Quelldatei/Zieldatei angegeben");
} else {
if (Integer.parseInt(e.getMessage()) == 1) {
System.out.println("Fehler: Keine Zieldatei angegeben.");
} else {
System.out.println("Fehler in der Argumenten Angabe");
}
}
}
catch (FileNotFoundException e) {
System.out.println("Fehler: Datei nicht gefunden.");
}
catch (IOException e) {
System.out.println("Fehler: " + e.getMessage());
}
catch (Exception e) {
System.out.println("Unbekannter Fehler aufgetreten.");
}
}
} |
Aufgabe 2:
(a) Definieren Sie eine Klasse NegativeNumberException als geprüfte Exceptions. Sehen Sie mindestens zwei Konstruktoren vor, einen parameterlosen Konstruktor und einen Konstruktor, der eine Fehlermeldung als Parameter akzeptiert. NegativeNumberException werden wir in den folgenden Teilaufgaben verwenden.
(b) Schreiben Sie eine Methode, die zu einer endlichen Folge von nicht-negativen reellen Zahlen
als Resultat liefert. Prüfen Sie dabei, ob eine der Zahlen ai negativ ist und lösen Sie in diesem Fall eine NegativeNumberException aus.
(c) Schreiben Sie ein kleines Programm, das die Argumente der Kommandozeilen in Zahlen wandelt, für diese Zahlen dieMethode aus Teilaufgabe (b) aufruft und das Ergebnis auf die Standardausgabe ausgibt. Beispiel:
> java WurzelVonProdukt 1 2 3 4
Ergebnis: 4.89897949
Behandeln Sie in Ihrem Programm alle möglichen Exceptions, die auftreten können, insbesondere NegativeNumberException.
a)
|
1 2 3 4 5 6 7 8 9 10 11 |
// Deklaration der Exceptpion. Ziemlich easy, da wir den Funktionsumfang der
// Basis Exception nicht verändern
class NegativeNumberException extends Exception {
public NegativeNumberException() {
super();
}
public NegativeNumberException(String s) {
super(s);
}
} |
b)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
static double calculateSQRTCrossProduct(double[] v) throws NegativeNumberException {
// Wir nehmen den ersten Eintrag als Basis
double sum = v[0];
// Das ganze Array durchgehen
for (int i = 1; i < v.length; i++) {
double number = v[i];
// Wenn es sich um eine negative Zahl handelt, Exception werfen
if (number < 0)
throw new NegativeNumberException(String.valueOf(v[i]));
// Kurzform für sum = sum * number;
sum *= number;
}
// Die Wurzel zurückgeben
return Math.sqrt(sum);
// Tipp: für die sqrt Anweisung müssen wir nicht mehr auf negative
// Zahlen prüfen, da wir per Exception schon ausgeschlossen haben das
// sum negativ sein kann
} |
c)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public static void main(String[] args) {
// Array für die Zahlen reservieren
double numbers[] = new double[args.length];
// Versuche die übergebenen Parameter in doubles zu konvertieren
try {
for (int i = 0; i < args.length; i++) {
double number = Double.parseDouble(args[i]);
numbers[i] = number;
}
} catch (NumberFormatException e) {
// Abfangen von Parametern die keine Zahl sind (z.b. ADSF)
System.out.println("FEHLER! Parameter ist keine Zahl! (" + e.getMessage() + ")");
}
// -----
// Versuche das die Wurzel aus dem Kreuzprodukt der übergebenen Zahlen
// zu berechnen
try {
double result = calculateSQRTCrossProduct(numbers);
System.out.println("Ergebnis: " + result);
} catch (NegativeNumberException e) {
System.out.println("FEHLER! Parameter darf keine negative Zahl sein! (" + e.getMessage() + ")");
}
} |
(a)
|
1 2 3 4 5 6 7 8 9 10 |
public class NegativeNumberException extends Exception {
public NegativeNumberException() {
super();
}
public NegativeNumberException(String msg) {
super(msg);
}
} |
(b)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Folgezahlen {
private double[] zahlen;
public Folgezahlen(double[] zahlen) {
this.zahlen = zahlen;
}
public double wurzelProdukt() throws NegativeNumberException{
if (zahlen.length > 0) {
double prod = zahlen[0];
for (int i = 1; i < zahlen.length; i++) {
if (zahlen[i] < 0) {
throw new NegativeNumberException(String.valueOf(zahlen[i]));
}
prod *= zahlen[i];
}
return Math.sqrt(prod);
}
return 0;
}
} |
(c)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class FolgezahlenProgramm {
public static void main(String[] args) {
try {
double[] zahlen = new double[args.length];
for (int i = 0; i < args.length; i++) {
zahlen[i] = Double.parseDouble(args[i]);
}
Folgezahlen folge = new Folgezahlen(zahlen);
System.out.println("Ergebnis: " + folge.wurzelProdukt());
}
catch (NegativeNumberException e) {
System.out.println("Fehler: Negative Zahl entdeckt. (" + e.getMessage() + ")");
}
catch (NumberFormatException e) {
System.out.println("Fehler: Keine Zahl in der Parameter Angabe.");
}
catch (Exception e) {
System.out.println("Unbekannter Fehler aufgetreten.");
System.out.println(e.getLocalizedMessage());
}
}
} |
Aufgabe 3
Es sei der folgende Quelltext gegeben:
|
1 2 3 4 5 6 7 8 9 10 |
import java.io.*;
public class MacPro extends Laptop {
public static void main(String[] args) throws IOException {
new MacPro().crunch();
}
// insert code here
}
class Laptop {
void crunch() throws IOException { }
} |
Für welche der folgenden Methodendefinitionen, jeweils eingefügt für die Zeile mit dem Kommentar “insert code here”, lässt sich der Quelltext kompilieren?
(a) void crunch() { }
(b) void crunch() throws Exception { }
(c) void crunch(int x) throws Exception { }
(d) void crunch() throws RuntimeException { }
(e) void crunch() throws FileNotFoundException { }
Erläutern Sie jeweils in einem Satz, warum eine Kompilierung möglich bzw. nicht möglich ist.
a) Lässt sich problemlos kompilieren, da die eingefügte crunch() Methode die bestehende Methode überschreibt.
Würde aber z.B. innerhalb von der eingefügten crunch() Methode per super.crunch(); die super Methode aufgerufen,
müsste die eingefügte crunch() Methode ebenfalls dieselbe throws Klausel deklarieren.
——————————————————————–
b) Lässt sich aus zwei Gründen nicht kompilieren: Zum einen ist in der Main Methode keine throws Klausel
für die “normale” Exception (Superklasse aller Exceptions) enthalten. Zum anderen dürfen abgeleitete
Methoden keine throws Klauseln von Superklassen bestehender Exceptions der abzuleitenden Methode beinhalten.
Ein anderes Beispiel hierfür wäre wenn cie crunch() Methode in der Klasse Laptop die FileNotFoundException
werfen würde, und die crunch() Methode in der MacPro Klasse die IOException. FileNotFoundException ist eine
abgeleitete Exception von IOException
——————————————————————–
c) Lässt sich problemlos kompilieren, da es sich um eine neue crunch Methode handelt, die mit der aus der Laptop
Klasse nichts zu tun hat. Das in der main Methode trotzdem nur die IOException throws Klausel stehen muss liegt
daran, dass die crunch() Methode von Laptop, und nicht von MacPro augerufen wird.
——————————————————————–
d) Siehe a)
——————————————————————–
e) Siehe a) Außerdem: FileNotFoundException ist eine Abgeleitete Exception von IOException (Umkehrschluss von b))
(a) Wird nicht funktionieren, da im Methodenkopf nicht angegeben wurde, dass eine IOException() geworfen werden kann. Da diese Exception auch nicht im Methodenrumpf behandelt wird (Er ist leer) muss die Angabe im Methodenkopf gemacht werden.
(b) Möglich, denn IOException() ist eine Unterklasse von Exception().
(c) Nicht möglich, da bei crunch() kein Parameter übergeben wird.
(d) nicht möglich, da RuntimeException() keine Oberklasse der IOException ist.
(e) nicht möglich, da FileNotFoundException() eine Unterklasse von IOException ist.
Aufgabe 4
Gegeben sei der folgende Quelltext eines Java-Programms:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class PorkPie {
static String s = "";
public static void main(String[] args) {
try { doStuff(); }
catch (Exception ex) { s+= "c1 "; }
System.out.println(s);
}
static void doStuff() throws RuntimeException {
try {
s += "t1 ";
throw new IllegalArgumentException();
}
catch(IllegalArgumentException ie) { s += "c2 "; }
throw new IllegalArgumentException();
}
} |
Welche Ausgabe erzeugt dieses Programm? Erläutern Sie, wie es zu dieser Ausgabe kommt.
Wir untersuchen zuerst die Geschehnisse in der doStuff() Methode. Im Try/Catch Block wird zuerst der Statische
String s um den Text “t1 ” ergänzt. Daraufhin wird eine IllegalArgumentException geworfen (throw), die direkt in
der Methode gefangen wird (catch). In diesem catch Block wird der Klassen Statische String s um den Text “c2 ”
ergänzt.
Nachdem dieser Try/Catch Block abgearbeitet ist, wirft die Methode eine weitere IllegalArgumentException Exception.
Diesmal ist kein Try/Catch Block vorhanden um diese aufzufangen, weshalb das Programm eine Methoden Stack Ebene
höher zurück springt, und damit im Try/Catch Block landet, der den eigentlichen Methodenaufruf umschließt. In
dem dortigen catch Block wird der Klassen Statische String s dann schließlich mit dem Text “c1 ” komplettiert.
Zwar handelt es sich bei dem dortigen catch Block um einen catch Block für allgemeine Exceptions (und nicht für
IllegalArgumentException), das ist aber egal, da IllegalArgumentException eine abgeleitete Klasse von Exception ist.
Im Anschluss an den Try/Catch Block wird die Statische Variable s ausgegeben. Die Ausgabe ist “t1 c2 c1 “.
Als erstes wird doStuff() ausgeführt. Das sorgt dafür, dass “t1 ” zu s hinzugefügt wird. Dann wird eine IllegalRuntimeException geworfen. Um diese wird sich aber noch in doStuff() gekümmert, wobei “c2 ” zu s hinzugefügt wird. Am ende der doStuff() Methode wird eine weitere Exception geworfen die an Main weitergereicht wird. In Main werden alle Exceptions abgefangen, weshalb auch die IllegalRuntimeException behandelt wird. Dabei wird “c1 ” zu s hinzugefügt.
Am Ende wird s ausgegeben und es erscheint “t1 c2 c1 ” auf der Konsole.
