» Tutorial / Java Grundlagen / Exceptions

Exceptions bilden in Java eine der wichtigsten Technologien zur Fehlererkennung und Behandlung. Große Teile der Java - Standardbibliothek nutzen das Exception - Handling (Ausnahmebehandlung), um auftretende Abstürze im Programmverlauf sicher zu behandeln. Bevor wir ins Details gehen, wollen wir noch einige Grundlagen schaffen.


» Konzept der Fehlerbehandlung nach oben «

Auch in Java können die unterschiedlichsten Fehler auftreten. Um diese zu signalisieren, werden auftretende Probleme durch eine spezielle Klasse repräsentiert. Des weiteren werden die kritischen Programmbereiche, wo man mit dem Auftreten eines Fehlers rechnet, mit einem sogenannten Try - Block umschlossen. Sofern in diesem Block ein spezifizierter Fehler auftritt, erzeugt dieser eine Instanz vom Typ einer zugewiesenen Fehlerklasse. Innerhalb des Try - Blocks wird diese nun abgefangen und zu den entsprechenden Blöcken umgeleitet, wo der Fehler behandelt werden soll. Diese fangen die Exception und tragen daher den Namen Catch - Block.


» Fehlerklassen von Java nach oben «

Das Exception - Handling wird streng von der Standardbibliothek bestimmt. Alle Fehlerklassen werden in Java vom Objekt Throwable abgeleitet, welches umfangreiche Standardoperationen zur Behandlung von Fehlern beinhaltet. Alle als Ausnahme verwendeten Klassen reihen sich also in eine feste Hierarchie ein.

Java bildet nun eine eigene Hierarchie für die Fehlerbehandlung. Diese ist zum einen in zwei große Kategorien eingeteilt, die sich noch weiter unterteilen, so dass spezifische Fehler genauer beschrieben werden können. Die folgende Grafik stellt den Hierarchiebaum dar.

Hierarchie der Exceptions

Natürlich handelt es sich hierbei nur um einen Auszug, denn tatsächlich leiten sich dutzende spezialisierter Klassen von den beiden Hauptklassen Error und Exception ab. Die hier dargestellten Zweige sollen lediglich den Stamm darstellen. Jedem der zwei Hauptzweige kommt dabei eine fundamentelle Rolle zu.

Error
Exception
Runtime Exception

» Try und Catch nach oben «

Kommen wir nun zum praktischen Einsatz. Die Fehlerbehandlung setzt sich aus einem Try - Block und einem sich anschließenden Catch - Block für die Fehlerbehandlung zusammmen. Der Try - Block fungiert hierbei lediglich als Markierung des zu überwachenden Bereiches.


try
{
  // Anweisungen
}
catch(Typ Objekt)
{
  // Anweisungen
}
    

Zu Demonstrationszwecken wollen wir uns vorerst einiger Methoden der Java - Standardbibliothek bedienen, die konsequent die Exception - Klassen verwenden. Dazu zählt auch die folgende Methode Sleep. Sie hält das Programm für eine gewisse Anzahl von Millisekunden an. Tritt innerhalb der Ausführung der Methode ein Fehler auf, so wird eine Ausnahme vom spezifizierten Typ geworfen, die wir prompt auffangen und durch eine Meldung auf dem Bildschirm ausgeben.


public class MyClass
{
  public static void main(String[] args)
  {  	
    try
    {
      Thread.sleep(2000);
    }
    catch(InterruptedException e)
    {
      System.out.print("Fehler aufgetreten!");
    }		
  }
}
    

» detaillierte Fehlerobjekte nach oben «

Im gerade gezeigten Beispiel übergeben wir zusätzlich eine Klasseninstanz vom Typ des Fehlers. Das ist bei allen abgefangenen Ausnahmen üblich und sogar notwendig. Wird eine Ausnahme ausgelöst, so wird sie durch eine vollständige Klasseninstanz repräsentiert. Diese kann nun durch ihre Weitergabe wertvolle Informationen zum aufgetretenen Fehler beinhalten. Das erleichtert oft die korrekte Behebung des Fehlers.

Da alle Exceptions von der Klasse Throwable, sowie einer deren Subklassen Error oder Exception abgeleitet sind, stehen ihnen auch alle vordefinierten Methoden und Datenelemente zur Verfügung. Diese speichern dann alle relevanten und notwendigen Informationen.


» Exceptions auffangen nach oben «

Wir hatten es bereits in einem Beispiel gesehen. Die geworfenen Exceptions werden über eine typisierte Catch - Anweisung abgefangen, sofern die Ausnahme mit ihrem Typ übereinstimmt.

Nun lassen Sie uns den Fall betrachten, in dem wir einen Try - Block haben, wo mehrere Ausnahmen auftreten können. Um auf alle Ausnahmen passend reagieren zu können, sollte für jeden neuen Klassentyp eine eigene Catch - Klausel vorgesehen werden. Dabei darf jeder Exceptiontyp nur einmal auftauchen. Ansonsten werden die Catch - Blöcke einfach der Reihe nach hinter dem Try - Block angeordnet.


try
{
  // Anweisungen
}
catch(Typ1 Objekt)
{
  // Anweisungen
}
catch(Typ2 Objekt)
{
  // Anweisungen
}
    

Tritt nun ein Fehler auf, so wird die ausgeworfene Exception der Reihe nach mit allen Catch - Blöcken abgeglichen und in dem Block behandelt, der mit dem Typ der Exception übereinstimmt. Alle anderen und nicht zutreffenden Blöcke bleiben ungenutzt. Welche Ausnahmen es hier gibt, werden wir bei der Besprechung von Exception - Hierarchien sehen.


» benutzerdefinierte Exceptions nach oben «

Wenn Sie selber Exceptions erstellen möchten, um zum Beispiel eine spezielle Fehlerbehandlung für Ihre eigenen Klassen zu implementieren, müssen Sie sich dennoch an die Vorgaben der Sprachspezifikation halten. Das heißt, dass sich auch Ihre neuen Exceptions in die Java - Hierarchie einordnen müssen.

Um nun eine neue Exception zu implementieren, müssen Sie lediglich die neue Klasse von einer der Klassen der Exception - Hierarchie ableiten. Dies kann direkt von Throwable erfolgen, sollte allerdings entweder von Exception oder einer ihrer Subklassen geschehen. Damit werden ihre Klassen ebenfalls als Fehlerobjekte für benutzerdefinierte Ausnahmen gekennzeichnet. Abzuraten ist hier eine Ableitung von der Error - Klasse, da diese an sich nur schweren Fehlern in der JVM vorbehalten ist.

Es empfiehlt sich, die Referenz der Exception - Hierarchie samt ihrer Methoden zu studieren, um eine Übersicht der enthalten Operationen vor Augen zu haben. Die Throwable - Klasse definiert unter anderem zwei Konstruktoren, die einen beliebigen String mit der Beschreibung des Fehlers übernehmen. Definiert man eigene Exceptions, so sollten diese die entprechenden Konstruktoren redefinieren.

Das folgende Beispiel geht diesen Weg und definiert eine neue Ausnahme. Diese leiten wir gleich von Exception ab und überschreiben sowohl den Standardkonstruktor als auch die Variante, welche einen String übernimmt.


class MyException 
extends Exception
{
  public MyException()
  {
		
  }
  public MyException(String s)
  {
    super(s);	
  }
}
    

Bei der Redefinition des Standardkonstruktors sind keine speziellen Anweisungen auszuführen. Der an den zweiten Konstruktor übergebene String mit der Fehlermeldung reichen wir hingegen einfach an die Basisklasse weiter.


» Exceptions auslösen nach oben «

Um nun eine Exception auslösen zu können, verwendet man den Throw - Befehl. In dessen syntaktischer Ausführung wird mit dem New - Befehl eine neue Instanz der Fehlerklasse erzeugt. Dieser kann man dann, je nach Konstruktor, bestimmte Parameter übergeben. Nachdem die Ausnahme ausgelöst wurde, wird sie in einer passenden Catch - Klausel behandelt.


try
{
  // Anweisungen
  throw new Klasse();
}
catch(Typ Objekt)
{
  // Anweisungen
}
    

Als nächstes werden wir unser Beispiel weiter ausbauen. Wir erweitern unsere benutzerdefinierte Klasse nun um den Rest des Quellcodes. Dort werden wir eine einfache Ausnahme auslösen und in der Catch - Klausel behandeln lassen.


class MyException 
extends Exception
{
  public MyException()
  {
		
  }
  public MyException(String s)
  {
    super(s);	
  }
}

public class MyClass
{
  public static void main(String[] args)
  {  	
    try
    {
      throw new MyException("Fehler aufgetreten...");
    }
    catch(MyException e)
    {			
      System.out.print(e.getMessage());	
    }		
  }
}
    

Fehler aufgetreten...
    

Beachten Sie den Code im Catch - Block. Dort nutzen wir eine Methode der Throwable - Klasse, welche wir durch die Ableitung von ihr erben. Sie liefert uns den übergebenen String zurück.


» Hierarchien nach oben «

Wie wir nun wissen, so befinden sich alle Exceptions in einer Hierarchie. Was das nun für die Behandlung von Ausnahmen bedeutet, soll hier geklärt werden.

Definieren wir mehrere Catch - Blöcke, in der auch Subklassen bahandelt werden sollen, so bewegt man sich vom Speziellen zum Allgemeinen. Das heißt, man sollte der Reihe nach erst die Subklassentypen auflisten und sich dann durch die Klassenhierarchie nach oben bewegen. Dadurch wird sichergestellt, dass erst die spezialisierten Varianten behandelt werden, da sie der Fehlerbeschreibung am nächsten kommen.

Die Exceptions von Java basieren fast vollständig auf der Klasse Exception und definitiv auf Throwable. Dadurch hätte man die Möglichkeit, alle nur denkbaren Fehler über eine einzige Catch - Anweisung abzufangen. Dennoch sollte man davon keinen Gebrauch machen, da eine detaillierte Behandlung viel zu umfangreich wäre. Die Angabe der Wurzelklasse macht nur am Ende einer Catch - Behandlung Sinn, da dort dann alle Fehler gesammelt abgefangen werden können, die in der Behandlung nicht gesondert betrachtet wurden.

Unser folgendes Beispiel zeigt diese Technik auf, indem wir mehrere Catch - Klauseln verwenden. Die erste ist auf unsere eigene Exception spezialisiert. Erst der letzte Block behandelt alle nicht von uns aufgeführten Exceptions.


class MyException 
extends Exception
{
  public MyException()
  {
		
  }
  public MyException(String s)
  {
    super(s);	
  }
}

public class MyClass
{
  public static void main(String[] args)
  {  	
    try
    {
      throw new MyException("Fehler aufgetreten...");
    }
    catch(MyException e)
    {			
      System.out.print(e.getMessage());	
    }		
    catch(Exception e)
    {
      System.out.print("Unbekannter Fehler aufgetreten...");	
    }
  }
}
    

Fehler aufgetreten...
    

» Exceptions ankündigen nach oben «

Alle Methoden in Java, die im Laufe ihrer Ausführung eine Exception auslösen könnten, müssen diese explizit ankündigen. Dies geschieht bei der unmittelbaren Definition der Methode, und zwar durch das Throws - Schlüsselwort im Kopf der Definition. Das hängt unter anderem mit dem Java - Sicherheitskonzept zusammen, denn diese Methoden sind mögliche Fehlerquellen. Außerdem müssen diese Methoden durch einen Try - Block gesichert werden, sonst gibt es einen Compilerfehler. Dadurch wird die definitive Behandlung durch einen Catch - Block sichergestellt, denn alle nicht behandelten Ausnahmen führen unweigerlich zur sofortigen Beendigung des Programms.


[Modifikator] Typ Methode() throws Exception
{
  // Anweisungen
}
    

Es müssen alle Exceptions aufgelistet werden, die die Methode auslösen kann. Handelt es sich um mehr als eine, so sind diese dann durch ein Komma voneinander zu trennen. Diesem Konzept folgen auch alle Klassen der Java - Bibliothek.

An dieser Stelle wollen wir uns noch die Differenzierung der Exceptions anschauen. Java teilt alle Exceptions in zwei Kategorien. » unkontrollierte Ausnahmen

Ausnahmen, die sich von Error oder RuntimeException ableiten sollte man nie ankündigen. Denn entweder handelt es sich um so schwerwiegende Fehler (Error) das man sie ohnehin nicht bahendeln kann, oder es handelt sich um Fälle die lediglich Kodierungsfehler (RuntimeException) betreffen. Diese brauch der Programmierer also nicht zu behandeln.

» kontrollierte Ausnahmen

Alle anderen Ausnahmen hingegen können und sollten Sie sogar behandeln. Alle Fehler dieser Kategorie spezialisieren sich auf Fälle, die behandelt und behoben werden können. Diese können von ihnen kontrolliert und angekündigt werden.


» Finally nach oben «

Als letztes wollen wir uns einer speziellen Klausel in der Ausnahmebehandlung widmen. Sie ist optional und wird immer ausgeführt, egal ob in der Ausnahmebehandlung einen Fehler registriert oder nicht. Wenn in einer Methode am Ende wichtige Ressourcen wieder frei gegeben werden sollen, sich vorher aber eine Exception ergibt, so wird die Methode abgebrochen und die Exception behandelt. Damit nun die fehlenden Operationen nicht vergessen werden, kann man immer einen Finally - Block anhängen. Dieser wird definitiv nach dem Try - Block ausgeführt.


try
{
  // Anweisungen
}
catch(Typ Objekt)
{
  // Anweisungen
}
[finally]
{
  // Anweisungen
}
    

In den folgenden Fällen wird die Finally - Klausel ausgeführt.

  • Es wird keine Ausnahme ausgelöst. Der gesamte Try - Block wird ausgeführt und am Ende der Finally - Block. Danach verzweigt das Programm zur ersten Anweisung hinter dem Try - Block.
  • Im Try - Block tritt eine Ausnahme auf. Der Block bricht ab und verzweigt zur Catch - Anweisung, wo die Ausnahme behandelt wird. Danach wird die Finally - Klausel ausgeführt. Nun geht es nach dem Try - Block weiter.
  • Es tritt eine Ausnahme auf, die jedoch von keiner passenden Catch - Klausel behandelt werden kann. Das Programm verzweigt zur Finally - Klausel, führt dort alle Anweisungen aus und fährt dann nach dem Try - Block fort.

class MyException 
extends Exception
{
  public MyException()
  {
		
  }
  public MyException(String s)
  {
    super(s);	
  }
}

public class MyClass
{
	public static void main(String[] args)
  {  	
    try
    {
      throw new MyException();
    }
    catch(MyException e)
    {			
      System.out.println("Catch...");	
    }		
    finally
    {
      System.out.println("Finally...");	
    }
  }
}
    

Catch...
Finally...
    

« Kapitel Kapitelübersicht nach oben