5. Ausnahmebehandlung mit Exceptions 5.1 Auf Laufzeitfehler reagieren Während des Programmablaufs können Fehler auftreten, die zum Zeitpunkt des Kompilierens nicht abzusehen sind (Laufzeitfehler) tritt Fehler auf, wird Exception ausgelöst Auslösen einer Exception wird throw genannt Exceptions werden immer innerhalb einer Methode ausgelöst Behandeln einer Exception wird catch gennant Klassifizierung der Exceptions in Java existiert Klassenhierarchie für Exceptions durch Auslösen einer Exception wird Exception-Objekt erzeugt Klasse Erläuterung Throwable Superklasse aller Fehlerklassen; besitzt Methode getmessage(), die optionalen Informationstext ausgibt Error Superklasse aller Exceptions, die hauptsächlich im Zusammenhang mit der VM auftreten; sollten nicht abgefangen werden (Standardbehandlung) Exception Exceptions, die im Programm abgefangen werden können, sind Subklassen der Klasse Exception; eigene Exceptions von ihr abgeleitet RuntimeException Treten aufgrund von Programmierfehlern auf; sollten durch ausreichende Tests und sorgfältige Programmierung vermeidbar sein Andere Diese Ausnahmen treten durch nicht kontrollierbare Situtationen ein (z.b. Öffnen einer nicht existierenden Datei) Unterschied Ausnahmen und Fehler Exception ist ein behebbarer Fehler, der abgefangen werden kann / muss; Programm kann nach Fehlerbehandlung stabil weiterlaufen Error ist in der Regel schwerer Fehler, der zumeist zum Beenden des Programms führt Kontrollierte und unkontrollierte Exceptions Ausnahmen vom Typ Error und RuntimeException sind unkontrollierte Exception; alle anderen sind kontrollierte Exceptions unkontrollierte Exception müssen nicht abgefangen werden Exceptions vom Typ Error können nicht sinnvoll behandelt werden, da Fehlerursache im VM liegt RuntimeException kann man durch sorgfältiges Programmieren vermeiden; für unkontrollierte Exceptions existiert Standardbehandlung kontrollierte Exceptions muss man im Programm behandeln; anderfalls Compilerfehler Beispiel für das Auftreten einer Exception String s = "ABC"; int i = Integer.parseInt(s); System.out.println("Die Zahl ist: " + i); Marcel Wieczorek 05.06.2008 42 S e i t e
Ausgabe Exception in thread "main" java.lang.numberformatexception: For input string: "ABC" at java.lang.numberformatexception.forinputstring(numberformatexception.java:48) at java.lang.integer.parseint(integer.java:447) at java.lang.integer.parseint(integer.java:497) at de.assona.exceptions.exceptionbeispiel.main(exceptionbeispiel.java:13) 5.2 Exceptions abfangen und behandeln Exceptions, die direkt oder indirekt von der Klasse Exception abgeleitet sind, muss man behandeln oder weitergeben (nicht bei Klassen die von RuntimeException oder deren Subklassen abgeleitet wurden) welche Exception eine Methode auslösen kann steht in der Java Dokumentation (throws Exception) Syntax für eine Exception-Behandlung // regulärer Programmcode catch (Exception e) { // Behandlung für Exception vom // jeweiligen Typ finally { // wird immer ausgeführt wenn mehrere Exceptions abgefangen werden müssen, müssen catch-blöcke so angeordnet werden, dass Superklassen von Exceptions nach jeweiligen Subklassen abgefangen werden (umgekehrte Hierarchie) wichtige Methoden der Klasse Throwable Methode Erläuterung String getmessage() Gibt an, was passiert ist; liefert Text, der beim Erzeugen des Exception- Objekts übergeben wurde String tostring() Liefert String, der sich aus Namen der Exception-Klasse und der Ausgabe von getmessage() zusammensetzt printstacktrace() Gibt an, wo etwas passiert ist; gibt Inhalt des Stacks auf Standardfehlerausgabe aus int[] a = new int[2]; a[1] = 2; a[2] = 3; catch (ArrayIndexOutOfBoundsException e) { System.out.println("Sie haben die Feldgrenzen verletzt."); System.out.println(e.getMessage()); System.out.println(e.toString()); Ausgabe Sie haben die Feldgrenzen verletzt. 2 java.lang.arrayindexoutofboundsexception: 2 Marcel Wieczorek 05.06.2008 43 S e i t e
Exceptions weitergeben public ThrowsBeispiel() throws ClassNotFoundException { Class<?> specialclass; // Die Klasse xxx existiert nicht specialclass = Class.forName("xxx"); /** * @param args */ ThrowsBeispiel tb = new ThrowsBeispiel(); catch (ClassNotFoundException e) { System.out.println("Die Klasse wurde nicht gefunden."); Tipps für die Weitergabe von Exceptions keine RuntimeExceptions weitergeben (Standardbehandlung) Methoden in abgeleiteten Klassen können nicht mehr Exceptions weitergeben als in der Basisklasse; daher in bestimmten Fällen in der Basisklasse Exceptions angeben, die erst in der abgeleiteten Klasse auftreten können Warum überhaupt weitergeben? Wenn in eigener Klasse ein Zustand eintritt, der dem Anwender der Klasse übermittelt werden soll, kann Exception ausgelöst werden; diese muss der Anwender abfangen (daher muss Exception zunächst weitergegeben werden) Exceptions auslösen public void thrownew(int i) throws IndexOutOfBoundsException { if (i < 1 && i > 100) throw new IndexOutOfBoundsException( "i liegt nicht im Interverall 1.. 100"); System.out.println("i hat den Wert " + i); ThrowNewBeispiel tb = new ThrowNewBeispiel(); tb.thrownew(125); catch (IndexOutOfBoundsException e) { e.printstacktrace(); 5.3 eigene Exceptions erzeugen um eigene Exceptions zu erzeugen, leitet man neue Klasse von der Klasse Exception ab neue Exceptions sollte nur von der Klasse Exception selbst und deren Subklassen abgeleitet werden, da diese behandelt werden müssen Name der neuen Exception sollte sich aus Beschreibung der Funktionalität und Namen der alten Klasse zusammensetzen (z.b. class RealNumberFormatException extends NumberFormatException) Marcel Wieczorek 05.06.2008 44 S e i t e
Beispiel package de.assona.exceptions; /** * @author Marcel Wieczorek */ public class IndexOutOfMinMaxException extends Exception { public IndexOutOfMinMaxException() { super(); public IndexOutOfMinMaxException(String message) { super(message); public IndexOutOfMinMaxException(int min, int max) { super("der Index liegt nicht zwischen " + min + " und " + max); 6. Assertions mit Assertions kann man während der Entwicklungszeit zu Testzwecken gezielt überwachen Behauptung wird als so Bedingung formuliert, dass sie true liefert, wenn das Programm ordnungsgemäß abläuft trifft Behauptung nicht zu, tritt AssertionError auf; Programm wird sofort beendet Auswertung der Assertions wird bei Ausführung des Interpreters eingeschaltet (standardmäßig ausgeschaltet) dürfen daher auch nicht verwendet werden, um Programmablauf zu sichern (könnten ausgeschaltet werden) sollten keine Seiteneffekte bei Auswertung der Ausdrücke haben (z.b. keine Methode aufrufen, die den Zustand des Programms ändert) 6.1 Assertions einsetzen final int TEMPARTURE_STANDARD = 20; int i = 0; // Parameter auslesen if (args.length > 0) i = Integer.parseInt(args[0]); if (!(i >= 10 && i <= 50)) i = TEMPARTURE_STANDARD; catch (NumberFormatException e) { System.out.println("Als Parameter bitte " + "eine Zahl zwischen 10 und 50 eingeben"); // prüfen, ob i zwischen 10 und 50 assert (i >= 10 && i <= 50) : "ASSERT: Die Zahl " + i + " lag nicht zwischen 10 und 50"; System.out.println("Eingestellte Temperatur: " + i); Marcel Wieczorek 05.06.2008 45 S e i t e
6.2 Assertions bei der Programmausführung auswerten Einschalten java -ea[:classname] [:PackageName] Ausschalten java -da[:classname] [:PackageName] 7. Mit Dateien arbeiten 7.1 Die Klasse File und ihre Konstruktoren File-Objekt repräsentiert tatsächliche Datei im Dateisystem Wurde eingeführt, um Dateioperationen plattformunabhängig durchzuführen (Einschränkungen z.b. bezüglich der Rechtevergabe) File-Objekt wird durch einen Pfadnamen spezifiziert (kann absolut oder relativ sein) Konstruktorübersicht Konstruktor Erläuterung File ( String pathname ) Erzeugt ein File-Objekt aus einem Dateinamen. File ( String parent, String child ) Setzt ein neues File-Objekt aus einem Basisverzeichnis und einem weiteren Teil zusammen, der auch wieder ein Verzeichnis oder ein Dateiname sein kann. File ( URI uri ) Fragt von uri den Pfadnamen (uri.getpath()) und erzeugt ein neues File-Objekt. Ist uri gleich null, folgt eine NullPointerException. Ist die URI falsch formuliert, gibt es eine IllegalArgumentException. Pfadtrenner Um plattformunabhängig zu bleiben, sollte Pfadtrenner aus öffentlicher Konstante ausgelesen werden File.separatorChar File.separator System.getProperty("file.separator") 7.2 Die Methoden der Klasse File Methode Erläuterung String getabsolutepath() Liefert den absoluten Pfad. Ist das Objekt kein absoluter Pfadname, so wird ein String aus aktuellem Verzeichnis, Separator-Zeichen und Dateinamen des Objekts verknüpft. String getname() Gibt den Dateinamen zurück. String getparent() Gibt den Pfadnamen des Vorgängers zurück. String getpath() Gibt den Pfadnamen zurück. boolean isabsolute() true, wenn der Pfad in der systemabhängigen Notation absolut ist. boolean exists() Liefert true, wenn das File-Objekt eine existierende Datei oder einen existierenden Ordner repräsentiert. boolean isdirectory() Gibt true zurück, wenn es sich um ein Verzeichnis handelt. boolean isfile() true, wenn es sich um eine»normale«datei handelt (kein Verzeichnis und keine Datei, die vom zugrunde liegenden Betriebssystem als besonders markiert wird; Blockdateien, Links unter Unix). Marcel Wieczorek 05.06.2008 46 S e i t e
7.3 Die Klasse RandomAccessFile und ihre Konstruktoren Es wird ein Dateizeiger verwaltet wird, den man selbst setzen kann Dateizugriff ist wahlfrei (im Gegensatz zum Datenstrom bei File, der eine strenge Sequenz erfordert) Konstruktoren Konstruktor RandomAccessFile ( String name, String mode ) RandomAccessFile ( File file, String mode ) Erläuterung Öffnet die Datei. Ob die Datei zum Lesen oder Schreiben vorbereitet ist, bestimmt der String mode mit gültigen Belegungen»r«oder»rw«. Öffnet die Datei. Ob die Datei zum Lesen oder Schreiben vorbereitet ist, bestimmt der String mode mit gültigen Belegungen»r«oder»rw«. 7.4 Die Methoden der Klasse RandomAccessFile Aus dem RandomAccessFile lesen Methode Erläuterung int read() Liest genau ein Byte und liefert es als int zurück. int read( byte[] b ) Liest b.length() viele Byte und speichert sie im Feld b. int read Liest len Byte aus der Datei und schreibt sie in das Feld b ab der ( byte[] b, int off, int len ) Position off. Wurde mehr als ein, aber weniger als len Bytes gelesen, wird die gelesene Größe als Rückgabewert zurückgegeben. final double readdouble() (, final float readfloat()) Liest einen primitiven Datentyp. final void readfully Versucht, den gesamten Puffer b zu füllen. ( byte[] b ) final String readline() Liest eine Textzeile, die das Zeilenendezeichen \r oder \n beziehungsweise eine Kombination \r\n abschließen. Die letzte Zeile muss nicht so abgeschlossen sein, denn ein Dateiende zählt als Zeilenende. 7.5 Grundlagen zu Streams sequenzielle Ein- und Ausgaben werden Streams genannt Für die byteorientierte Verarbeitung (z.b. PDF- oder MP3-Dateien) gibt es andere Klassen als für zeichenorientierte Verarbeitung (Textdokumente, z.b. HTML) Sinnvoll, da Einlesen von Unicode-Dateien ist vereinfacht wurde (Daten müssen nicht auf festgelegten Zeichensätzen arbeiten; Konvertierung von Unicode nach Byte im Hintergrund) Unterschieden werden byteorientierte Streams und zeichenorientierte Streams Streams und ihre Klassen Aktion Bytes (oder Byte-Arrays) Zeichen (oder Zeichen-Arrays, Strings) Aus Dateien lesen [File]InputStream [File]Reader In Dateien schreiben [File]OutputStream [File]Writer Marcel Wieczorek 05.06.2008 47 S e i t e
7.6 Character-Streams schreiben Von der Superklasse Writer werden alle Klassen zur Ausgabe von Character-Streams abgeleitet (z.b. FileWriter) FileWriter ist ein spezieller Writer, der Ausgaben in eine Datei erlaubt Methoden aus Oberklassen werden nicht überschrieben bzw. implementiert Zusätzliche Konstruktoren Konstruktoren Konstruktor FileWriter ( String filename, boolean append ) FileWriter ( FileDescriptor fd ) Erläuterung Erzeugt einen Ausgabestrom und hängt die Daten an eine existierende Datei an, wenn append gleich true ist. Eine weitere Möglichkeit, Daten hinten anzuhängen, bietet die Klasse RandomAccessFile oder FileOutputStream. Erzeugt einen Ausgabestrom zum Schreiben in eine Datei. Existiert die Datei bereits, wird die Datei gelöscht. Abgeleitete Klassen der Klasse Writer Klasse Erläuterung BufferedWriter Schreibt Zeichen, Strings oder Arrays in einen gepufferten Character- Stream und ermöglicht so ein effektives Schreiben CharArrayWriter Die Zeichen werden in ein Character-Array geschrieben; dieser Puffer wächst automatisch während des Schreibens FilterWriter Schreibt gefilterte Character-Streams (abstrakt) OutputStreamWriter Schreibt die zeichen in einen Ausgabestrom und wandelt dabei den Character-Stream in einen Byte-Stream um FileWriter Eine von OutputStreamWriter abgeleitete Klasse, die die Ausgabe in eine Datei realisiert PipedWriter Dient zur Ausgabe in eine Pipe (Pipes werden zur Realisierung der Ein- und Ausgabeströme von Threads verwendet) PrintWriter Gibt alle primitiven Datentypen und Strings im Textformat aus StringWriter Schreibt Zeichen in einen Stringpuffer 7.7 Character-Streams lesen Abgeleitete Klassen der Klasse Reader Klasse Erläuterung BufferedReader Liest Zeichen, Strings oder Arrays aus einem Character-Stream, puffert diese und ermöglicht so ein effektives Lesen CharArrayReader Die Zeichen werden aus einem Character-Array gelesen FilterReader Filtert Character-Streams während des Lesens (abstrakt) InputStreamReader Eingabestrom wird als Byte-Stream gelesen und in einen Character-Stream umgewandelt FileReader Eine von InputStreamReader abgeleitete Klasse, die das Lesen aus einer Datei realisiert PipedReader Dient zum Lesen aus einer Pipe StringReader Gibt alle primitiven Datentypen und Strings im Textformat aus StringWriter Liest Zeichen aus einem String-Puffer Marcel Wieczorek 05.06.2008 48 S e i t e
7.8 Mit Character-Streams und Textdateien arbeiten OutputStreamWriter und FileWriter Writer fw = null; // FileWriter erzeugen fw = new FileWriter("tmp" + separator + "filewriter.txt"); fw.write("es war einmal..."); fw.append('\n'); catch (IOException e) { System.out.println("Datei konnte nicht erstellt werden."); finally { /* * FileWriter schließen: Mit dem Schließen des Writers wird die * Datei geschrieben. */ if (fw!= null) fw.close(); catch (IOException e) { e.printstacktrace(); InputStreamReader und FileReader Reader f = null; // FileReader erzeugen f = new FileReader("tmp" + separator + "filewriter.txt"); // Datei auslesen und Inhalt auf der // Konsole ausgeben for (int c; (c = f.read())!= -1;) System.out.print((char) c); catch (IOException e) { System.out.println("Datei konnte nicht gelesen werden."); finally { // FileReader schließen f.close(); catch (Exception e) { e.printstacktrace(); 7.9 Character-Streams puffern Ein- und Ausgabeströme werden gepuffert, um die Ein- und Ausgabe effektiver zugestalten Daten werden beim Ein- und Auslesen zwischengespeichert und gelangen erst dann zum Ziel, wenn Puffer voll ist oder entsprechende Methode aufgerufen wird Marcel Wieczorek 05.06.2008 49 S e i t e
Ausgabeströme für die Pufferung von Ausgabeströmen ist es erforderlich, Character-Streams zu schachteln Konstruktor der Klasse BufferedWriter muss ein weiteres Writer-Objekt übergeben werden (z.b. vom Typ FileWriter) wird die write()-methode aufgerufen, werden Daten nicht gleich an den FileWriter weitergeleitet, sondern erst dann, wenn Puffer geleert wird final char ca[] = { 'k', 'n', 'r', '0', '1' ; final FileWriter fw; final BufferedWriter bw; // FileWriter erzeugen fw = new FileWriter("tmp" + separator + "bufwriter.txt"); // BufferedWriter erzeugen bw = new BufferedWriter(fw); // char array 100 mal zeilenweise // in die Datei schreiben for (int i = 0; i < 100; i++) { bw.write(ca); bw.newline(); // BufferedWriter schließen bw.close(); catch (IOException e) { System.out.println("Datei konnte nicht geschrieben werden."); Eingabeströme final StringBuilder builder = new StringBuilder(); final FileReader fr; final BufferedReader br; // FileReader erzeugen fr = new FileReader("tmp" + separator + "bufwriter.txt"); // BufferedReader erzeugen br = new BufferedReader(fr); // Dateiinhalt auslesen und an // Stringbuilder anhängen String s = br.readline(); while (s!= null) { builder.append(s); builder.append("\n"); s = br.readline(); // BufferedReader schließen br.close(); catch (IOException e) { System.out.println("Datei konnte nicht gelesen werden."); System.out.println(builder); Marcel Wieczorek 05.06.2008 50 S e i t e
8. Nützliche Klassen und Packages 8.1 Zufallszahlen Random public long randomize_1() { Random random = new Random(); long value = random.nextlong(); return value; Math.random() public long randomize_2(long range) { long value = (long) (Math.random() * range) + 1; return value; 8.2 Datum und Zeit Date Speichern eines Datum-/Zeitwertes (als 64-Bit-Long-Variable) in ms (Millisekunden) seit 1970-01-01 0h UTC (negative Werte sind vor 1970) Ab JDK 1.1 wurde Calendar zur Bearbeitung von Datumswerten eingeführt und fast alle Methoden von Date als Deprecated markiert Calendar und GregorianCalendar Berechnungen mit Datums- und Zeitwerten SimpleDateFormat Formatierte Ein-/Ausgabe von Datums- und Zeitwerten Marcel Wieczorek 05.06.2008 51 S e i t e
Benutzung von Date, SimpleDateFormat und GregorianCalendar /* * Verschiedene Verfahren zur Erzeugung eines Kalender-Objekts: */ Calendar mycal1 = Calendar.getInstance(); // lokales Kalendersystem mit aktueller Zeit Calendar mycal2 = new GregorianCalendar(); // GregorianCalendar mit aktueller Zeit Calendar mycal3 = new GregorianCalendar(2008,Calendar.JUNE,6); // GregorianCalendar mit vorgegebener Zeit // Setzen von Jahr + Monat + Tag: mycal2.set( 2008, Calendar.JUNE, 6 ); // ändert so nicht Stunden, Minuten und Sekunden mycal2.settime( mycal2.gettime() ); // nicht mehr unbedingt notwendig seit JDK 1.2 // Zeit setzen mit Date: mycal2.settime( new Date() ); // Einzelne Felder extrahieren: int year = mycal2.get( Calendar.YEAR ); int mnth = mycal2.get( Calendar.MONTH ) + 1; // nicht vergessen die 1 zu addieren int date = mycal2.get( Calendar.DATE ); System.out.println( date + "." + mnth + "." + year ); // für Formatierung ist SimpleDateFormat besser // Calendar ist gut geeignet, um Berechnungen durchzuführen, // z.b. um den um ein Jahr erhöhten Zeitwert zu berechnen: mycal2.add( Calendar.YEAR, 1 ); // Umwandlung von Calendar in Date: Date mydate = mycal2.gettime(); System.out.println( mydate ); Berechnung von Datums-Differenzen Calendar cal_1 = new GregorianCalendar(); Calendar cal_2 = new GregorianCalendar(); cal_1.set( 2007, Calendar.MARCH, 1, 0, 0, 0 ); // erster Zeitpunkt cal_2.set( 2008, Calendar.APRIL, 2, 0, 0, 0 ); // zweiter Zeitpunkt long time = cal_2.gettime().gettime() - cal_1.gettime().gettime(); // Differenz in ms long days = Math.round( (double)time / (24d * 60d * 60d * 1000d) ); // Differenz in Tagen System.out.println( "Zeit-Differenz in Tagen: " + days ); 8.3 Die Klasse System und ihre Methoden beinhaltet nützliche Felder und Methoden kann nicht instanziiert werden zu den Werkzeugen, die die Klasse System liefert gehören Standard-Input-, Standard-Output und Error-Output-Streams sowie der Zugriff auf extern definierte Properties und Umgebungsvariablen Marcel Wieczorek 05.06.2008 52 S e i t e
Felder Feldname static PrintStream err static InputStream in static PrintStream out Erläuterung Der Standard-Error-OutputStream Der Standard-InputStream Der Standard-OutputStream Methoden Methode Erläuterung Properties getproperties() Liefert Properties-Objekt zurück, das wie eine normale Map verwaltet wird und Systemeigenschaften enthält void exit (int status) Dient zum Verlassen der Anwendung (lt. Konvention wird bei normaler Beendigung 0 übergeben) void gc() Ruft den Garbage-Collector auf Übung 1. In einem Programm Test sollen die Schlüssel-Wert-Paare der Systemumgebungseinträge sowie die der Systemeigenschaften in eine Datei geloggt werden. Der dazu notwendige Logger ist bereits fertig implementiert. Ihr müsst euch nur noch mit dessen Funktionalität vertraut machen. In der Klasse Environment müssen zum Auslesen der Eigenschaften die beiden Methoden logenv() und logprops() implementiert werden, die die Einträge formatiert an den Logger übergeben. 2. Im nächsten Schritt soll die zeitliche Differenz zwischen der ersten und der letzten Log- Ausgabe ermittelt und auf der Konsole ausgegeben werden. Zu diesem Zweck loggt der Logger die aktuelle Systemzeit in Nanosekunden bereits mit. Ihr müsst aus der in Schritt 1 erstellten Log-Datei nur noch diese beiden Zeiten extrahieren und die Differenz berechnen. Zu diesem Zweck ist in der Klasse Test bereits die Methode nanodifference(string file) implementiert worden. Sie muss allerdings noch fertiggestellt werden. 3. Die in Schritt 1 erstellte Log-Datei soll nun in eine exakte Kopie überführt werden. Dazu ist bereits eine Klasse Copy erstellt worden, in der ihr die Methode copyfile(string filename) mit entsprechender Funktionalität füllen sollt. Der Kopiervorgang soll zeilenweise und gepuffert durchgeführt werden. Am besten schreibt ihr jede gelesene Zeile gleich in die Kopie-Datei. Marcel Wieczorek 05.06.2008 53 S e i t e