Home > Informatik > Einführung in die OOP > Exception-Handling > 8.3 Eigene Exception-Klassen

8.3 Erzeugen eigener Ausnahme-Klassen

Teil 1 - Teil 2 - Teil 3 - Teil 4 - Teil 5 - Teil 6 - Teil 7 - Teil 8

Sie erinnern sich bestimmt noch an die Folge 2 dieses Kurses, als wir eine kleine Waage implementiert haben, die aus dem Gewicht und der Körpergröße einer Person verschiedene Werte berechnet (BMI, Idealgewicht etc.).

An diesem Beispiel soll hier einmal demonstriert werden, wie man eigene Ausnahme-Klassen implementieren kann. Dazu muss man allerdings das Konzept der Vererbung verstanden haben, denn eigene Ausnahme-Klassen sind stets Unterklassen der Klasse Exception.

Die Programmieranfänger(innen) unter Ihnen sollten diesen Abschnitt besser erst nach der Folge "8. Vererbung" lesen.

Eine eigene Exception-Klasse

public class UnrealistischeWerteException extends Exception
{
   public UnrealistischeWerteException(String message)
   {
      super(message);
   }
}

Dieser Konstruktor übernimmt den Ausgabe-String und reicht ihn an die Oberklasse
Exception weiter. Der übergebene String enthält dann die konkrete Fehlermeldung wie beispielsweise "Das Gewicht muss mindestens 3 kg betragen".

Klasse Waage (Auszug)

public class Waage
{
   private double gewicht;

   public void setGewicht(double gewicht) 
         throws UnrealistischeWerteException
   {
      if (gewicht < 3 )
         throw new UnrealistischeWerteException(
            "Das Gewicht muss mindestens 3 kg betragen!");
      if (gewicht > 130 )
         throw new UnrealistischeWerteException(
            "Das Gewicht darf nicht höher als 130 kg sein!");            

      this.gewicht = gewicht;
   }
   
   // Weitere Setter- und Getter- sowie Ausgabe-Methoden
}

Hier wird von der eigenen Klasse UnrealistischeWerteException Gebrauch gemacht. Wenn das Gewicht kleiner ist als 3 kg, wird eine UnrealistischeWerteException geworfen, die mit dem String "Das Gewicht muss mindestens 3 kg betragen!" initialisiert wird.

Ist das Gewicht dagegen größer als 130 kg, wird eine UnrealistischeWerteException geworfen, die ein zu hohes Gewicht anzeigt.

Schreiben wir nun ein erstes Testprogramm für die Klasse Waage:

Testprogramm 1 - BlueJ-Version

public class ExceptionTest
{
    Waage w = new Waage();
    
    public ExceptionTest()
    {
        w.setGewicht(2);
    }
}

Die BlueJ-Version benötigt keine main()-Methode, hier findet der fehlerhafte Aufruf im Konstruktor der Testklasse statt. Bereits beim Versuch, die Klasse zu kompilieren, meldet BlueJ einen Fehler: "Nicht gemeldete Ausnahme UnrealistischeWerteException. Muss abgefangen oder deklariert werden, um ausgelöst zu werden."

Mit dieser Meldung ist Folgendes gemeint:

Die Methode setGewicht() ist mit throws UnrealistischeWerteException deklariert. Damit teilt die Methode dem Compiler mit: "Beim Aufruf dieser Methode kann eine UnrealistischeWerteException auftreten."

UnrealistischeWerteException erbt von Exception und ist damit eine kontrollierte Ausnahme. Solche Ausnahmen werden von Java bereits zur Übersetzungs-Zeit geprüft. Der Compiler akzeptiert einen Aufruf einer solchen Methode nur dann, wenn der mögliche Fehlerfall korrekt berücksichtigt wird.

In unserem Testprogramm wird die Methode setGewicht() aufgerufen, ohne die Exception zu behandeln. Deshalb meldet der Java-Compiler, dass die Exception "nicht gemeldet" wurde.

Testprogramm 2 - IntelliJ-Version

public class ExceptionTest
{
   public static void main(String[] args)
         throws UnrealistischeWerteException
   {
      Waage w = new Waage();
      w.setGewicht(170);
   }
}

Diese Version der Testklasse kann problemlos kompiliert werden. Die main()-Methode fängt die Exception nicht ab, die von setGewicht() geworfen wird, sondern gibt die Exception mit throws weiter an die aufrufende JVM.

Die JVM beendet dann das Programm und gibt die Fehlermeldung und einen Stacktrace aus:

UnrealistischeWerteException: Das Gewicht darf nicht höher als 130 kg sein!
	at Waage.setGewicht(Waage.java:12)
	at ExceptionTest.main(ExceptionTest.java:7)

Die throws-Angabe in der main()-Methode sorgt also nur dafür, dass das Programm trotzdem kompiliert werden kann. Eine eigene Fehlerbehandlung findet damit noch nicht statt, das Programm endet im Fehlerfall mit einer Exception-Ausgabe der JVM.

Testprogramm 3 - Version mit try-catch

public class ExceptionTest
{
   public static void main(String[] args)
   {
      Waage w = new Waage();

      try
      {
         w.setGewicht(170);
         System.out.println("Gewicht wurde gesetzt: 170 kg");
      }
      catch (UnrealistischeWerteException e)
      {
         System.out.println("FEHLER: Gewicht konnte nicht gesetzt werden.");
         System.out.println(e.getMessage());
      }

      System.out.println("Programm läuft weiter.");
   }
}

In dieser Version des Testprogramms wird die Exception in main() abgefangen. Das Programm stürzt nicht ab, sondern gibt eine verständliche Fehlermeldung aus und läuft danach weiter - was man an dem System.out.println()-Befehl sehen kann, der noch die Meldung "Programm läuft weiter." ausgibt, auch wenn der Laufzeitfehler aufgetreten ist.

Hier die Konsolen-Ausgabe dieses dritten Testprogramms:

FEHLER: Gewicht konnte nicht gesetzt werden.
Das Gewicht darf nicht höher als 130 kg sein!
Programm läuft weiter.

Achten Sie hier darauf, welche Methoden für welchen Teil dieser Meldungen verantwortlich sind:

FEHLER: Gewicht konnten nicht gesetzt werden

Diese Meldung wird in dem catch-Block des Testprogramms erzeugt.

Das Gewicht darf nicht höher als 130 kg sein!

Diese Meldung wurde von der Methode Waage.setGewicht()erzeugt, und zwar bei der Erzeugung des Exception-Objekts new UnrealistischeWerteException().

Programm läuft weiter

Das ist keine Fehlermeldung, sondern wurde von der main()-Methode produziert, um zu demonstrieren, dass das Programm trotz des Fehlers nicht abstürzt.

Gleichzeitiges Werfen mehrerer Ausnahmen

Wir verändern nun die Methode setGewicht() der Klasse Waage folgendermaßen:

public class Waage
{
   private double gewicht;

   public void setGewicht(double gewicht) 
         throws GewichtZuNiedrigException, GewichtZuHochException
   {
      if (gewicht < 3 )
         throw new GewichtZuNiedrigException();
      if (gewicht > 130 )
         throw new GewichtZuHochException();
      this.gewicht = gewicht;
   }
   
   // Weitere Setter- und Getter- sowie Ausgabe-Methoden
}

Die Methode kann jetzt zwei verschiedene Exceptions werfen, nämlich GewichtZuNiedrigException oder GewichtZuHochException. Wenn das Gewicht zu klein ist, wird die erste Exception geworfen, wenn das Gewicht den erlaubten Wert von 130 kg überschreitet, wird die zweite Exception geworfen.

Allerdings müssen wir die beiden Klassen für die neuen Exceptions noch implementieren:

public class GewichtZuNiedrigException extends UnrealistischeWerteException
{
    public GewichtZuNiedrigException()
    {
        super("Das Gewicht muss mindestens 3 kg betragen!");
    }
}

public class GewichtZuHochException extends UnrealistischeWerteException
{
    public GewichtZuHochException()
    {
        super("Das Gewicht darf höchstens 130 kg betragen!");
    }
}

Beide Klasse sind Unterklassen von UnrealistischeWerteException, die wiederum Unterklasse von Exception ist. Ein String-Parameter wird hier den Unterklassen nicht mehr übergeben, sondern die Klartext-Fehlermeldung wird direkt im Konstruktor der jeweiligen Unterklasse festgelegt.

Betrachten wir nun die neue Version des Testprogramms, auch hier gibt es etwas Neues:

public class ExceptionTestTC
{
    public static void main(String[] args)
    {
        Waage w = new Waage();

        try
        {
            w.setGewicht(1);
        }
        catch (GewichtZuNiedrigException e)
        {
            System.out.println(e.getMessage());
        }
        catch (GewichtZuHochException e)
        {
            System.out.println(e.getMessage());
        }        

        System.out.println("Programm läuft weiter.");
    }
}

Wenn eine Methode wie main() mehrere verschiedene Exceptions zugeworfen bekommt, dann können auf den try-Block mehrere verschiedene catch-Blöcke folgend, für jede Exception ein catch-Block. Wenn beim Ausführen von setGewicht() ein Laufzeitfehler verursacht wird, wird dann der jeweils entsprechende catch-Block ausgeführt.

In Java kann man dieses Konstrukt sogar noch einfacher darstellen:

public class ExceptionTestTC
{
    public static void main(String[] args)
    {
        Waage w = new Waage();

        try
        {
            w.setGewicht(1);
        }
        catch (GewichtZuNiedrigException |  GewichtZuHochException e)
        {
            System.out.println(e.getMessage());
        }

        System.out.println("Programm läuft weiter.");
    }
}

Hier werden beide Exceptions in einer catch-Anweisung abgefangen.

Wenn Sie das Konzept der Vererbung richtig verstanden haben, dann werden Sie auch den folgenden Code verstehen:

public class ExceptionTestTC
{
    public static void main(String[] args)
    {
        Waage w = new Waage();

        try
        {
            w.setGewicht(1);
        }
        catch (UnrealistischeWerteException e)
        {
            System.out.println(e.getMessage());
        }

        System.out.println("Programm läuft weiter.");
    }
}

Hier steht im catch-Block die Oberklasse UnrealistischeWerteException, auch das funktioniert.

GewichtZuNiedrigException und GewichtZuHochException sind Unterklassen von UnrealistischeWerteException. Das heißt: Jedes Objekt der Klasse GewichtZuNiedrigException IST auch ein Objekt der Oberklasse UnrealistischeWerteException.

Wenn w.setGewicht(1) ausgeführt wird, wirft diese Methode eine GewichtZuNiedrigException. Diese wird von Java aber automatisch als eine UnrealistischeWerteException akzeptiert, denn catch(UnrealistischeWerteException) darf alle Exceptions dieser Klasse einschließlich ihrer Unterklassen abfangen.

Wie ein kleiner Test zeigt, wird dennoch die korrekte Fehlermeldung ausgegeben. Der Nachteil dieses Verfahrens ist allerdings, dass im catch-Block nicht mehr auf die beiden unterschiedlichen Fälle eingegangen werden kann. Falls Sie also vorhaben, weitere Meldungen im Falle eines zu geringen / zu hohen Gewichts im catch-Block auszugeben, funktioniert das mit dieser Methode nicht mehr. Dann müssten Sie wieder zwei einzelne catch-Blöcke implementieren.

Seitenanfang -
Weiter mit dem Erzeugen eigener Ausnahmen ...