Ein wichtige Basis der objektorientierten Programmierung ist der Polymorphismus. Elementares Konzept für die Ausnahmebehandlung in Programmabläufen sind Exceptions … Also warum nicht beides zusammenbringen. Schauen wir uns in diesem Artikel an, wie sich die Deklaration von Ausnahmen in Methoden bei Anwendung des Polymorphismus auswirkt.
Das Beispiel:
Eine Bewegungsform für Tiere und Menschen ist das Laufen. Bei den meisten funktioniert es immer, aber beispielsweise durch eine Verletzung wie einen Beinbruch kann das Laufen eingeschränkt oder auch für eine kurze Zeitspanne unmöglich werden. Ebenfalls zu Einschränkungen führt übermäßige Trunkenheit.
Die Basiskontruktion
Wir erschaffen uns eine Schnittstelle, welche eine Methode für das Laufen deklariert. Diese Methode wird einen allgemeinen Fehler auswerfen.
Dieser Fehler wird sein eine checked Exception, d.h. der Compiler wird uns auf Syntax-Fehler im Quellcode hinweisen. Dazu muss der Fehler von der Klasse Exception abgeleitet werden. Zum einen kann es sich um den Fehler der Verletzung (mit der Unterklasse Beinbruch), zum anderen um den Fehler der Trunkenheit handeln.

Checked Exception in Realisierungs-Hierarchie – kein Polymorphismus
Fangen wir erst einmal mit einer Klasse an, welche die Schnittstelle realisieren soll. Nehmen wir dazu eine Katze.
Diese Katze schleicht ständig durchs Unterholz, so dass es passieren kann, dass sie aufgrund einer Verletzung nicht laufen kann.
Die Deklaration der Methode in der Schnittstelle wird dahingehend angepasst, dass nur eine Verletzung als Fehler für das Laufen akzeptiert wird.
Der Code für die Fehlerklasse sieht so aus:Der Konstruktor verlangt nach einer Message, welche an den Konstruktor der Oberklasse weitergeleitet wird.
public class Verletzung extends Exception{
public Verletzung(String message){
super(message);
}}
Und der Code der Katze inklusive zugehöriger Schnittstelle:
public interface IBewegung{
void laufen() throws Exception;
}
public class Katze implements IBewegung{
String name;
public Katze(String name){this.name = name;}
public void laufen() throws Exception{
throw new Verletzung(name + " hat sich veletzt beim Laufen");
}}
Und nun erstellen wir eine Katze und lassen diese laufen. Wenn wir die Methode einfach aufrufen, wird uns der Compiler darauf aufmerksam machen, dass beim Laufen ein Fehler auftreten kann, den wir abfangen müssen. Dies kann mit einem try…catch geschehen.
public static void main(String... args){
Katze cat = new Katze("Butterblume");
try{
cat.laufen();
}catch(Exception e){
System.out.println(e.getMessage());
}}
Bei Start des Programms wird uns ausgegeben, dass Butterblume sich verletzt hat (eine allgemeine Verletzung).
Konkretisieren wir nun die Verletzung, indem wir bei der Klasse Katze hinterlegen, dass sich die Katze bei ihren nächtlichen Streifzügen nur die Beine brechen kann.
Der Code für die Katze inkl. Schnittstelle (welche sich nicht ändert), sie dann wie folgt aus:
public interface IBewegung{
void laufen() throws Exception;
}
public class Katze implements IBewegung{
String name;
public Katze(String name){ this.name = name;}
public void laufen() throws Beinbruch{
throw new Beinbruch(name + " hat sich das Bein gebrochen");
}}
Die main-Methode mit der Instanziierung einer Katze ändert sich nicht. Jetzt wird nur ausgegeben, dass sich die Katze ein Bein gebrochen hat.
Unterschiedliche Fehlerklassen in der Methode laufen
Worauf ich hier zunächst hinaus möchte, ist, dass die Methode laufen in der Schnittstelle und der Klasse Katze (welche diese Schnittstelle und damit die dort angegebene Mehtode implementiert) unterschiedliche Fehlerklassen verwenden.
Die Schnittstelle definiert eine Verletzung; die Katze einen Beinbruch. Aber da Beinbruch eine Unterklasse von Verletzung ist, kann man das machen.
Doch wie sieht es aus, wenn die Katze allen Widrigkeiten trotzen wird und immer läuft – also niemals eine Ausnahme beim Laufen auftreten wird. D.h. die Methode laufen() in der Klasse Katze braucht keinen Fehler auswerfen … mmh, aber die Schnittstelle gibt es doch vor??
Wir brauchen auf Ausnahmen, die in einer zu überschreibenden Methode deklariert wurden, keine Rücksicht nehmen.
Also unsere Katze braucht beim Laufen keine Exception auszuwerfen.
public interface IBewegung{
void laufen() throws Exception;
}
public class Katze implements IBewegung{
String name;
public Katze(String name){this.name = name;}
@Override
public void laufen(){
System.out.println(name + " kann geradeaus laufen");
}}
Man beachte: Obwohl die Klasse die Schnittstelle realisiert und damit die in der Schnittstelle angegebene Methode mit der korrekten Signatur implementieren muss, weist und der Compiler nicht darauf hin, dass etwas fehlt. Ergo: wir müssen die Fehlerklasse in der Schnittstelle nicht berücksichtigen.
Das dazugehörige Hauptprogramm sieht dann wie folgt aus (kein try…catch mehr notwendig):
public static void main(String... args){
Katze cat = new Katze("Butterblume");
cat.laufen();
}}
Checked Exceptions in Realisierungs-Hierarchie – mit Polymorphismus
So, und nun mit Polymorphismus.
Polymorphismus haben wir realisiert, indem sich der Datentyp und der für die Instanziierung verwendete Konstruktor einer Objektreferenz unterscheiden (beide müssen in einer Vererbungs- oder Realisierungsbeziehung stehen).
Dies erreichen wir im Hauptprogramm mit folgendem Codefragment:
IBewegung katze = new Katze("Butterblume");
Anmerkung: Der Datentyp der Objektreferenz gibt an, welche Methoden aufgerufen werden können; der Konstruktor bestimmt deren Implementierung.
Unser Quellcode für die Katze inkl. der Schnittstelle hat immer noch folgendes Aussehen:
public interface IBewegung{
void laufen() throws Exception;
}
public class Katze implements IBewegung{
String name;
public Katze(String name){this.name = name;}
@Override
public void laufen(){
System.out.println(name + " kann geradeaus laufen");
}}
Wenn wir nun im Hauptprogramm den Fehler Verletzung nicht abfangen, weist uns der Compiler auf eine Syntax-Fehler hin.
Erläuterung
Der Compiler prüft den Quellcode auf Richtigkeit anhand der für die Variablen verwendeten Datentypen. Und das ist hier der Datentyp IBewegung. Für die Methode laufen wurde in der Schnittstelle hinterlegt, dass bei der Ausführung eine Verletzung ausgeworfen werden kann und das ist das einzig maßgebliche für den Compiler: es kann lt. Datentyp ein Fehler ausgeworfen werden, also muss dieser bei Methodenaufruf abgefangen werden.
Dann tuen wir dem Compiler den Gefallen und bauen ein try-catch ein.
public static void main(String... args){
IBewegung cat = new Katze("Butterblume");
try{
cat.laufen();
}catch(Exception e){
System.out.println(e.getMessage());
}}
Unsere Katze kann auch hier wiederum einen Beinbruch als spezieller Verletzung erleiden. Über die Vererbungshierarchie wird auch diese Ausnahme korrekt abgefangen.