Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 3. Auflage
 <<    <     >    >>   API  Kapitel 6 - Anweisungen

6.4 Sonstige Anweisungen



6.4.1 Die assert-Anweisung

Die Ausführungen in diesem Abschnitt können beim ersten Lesen übersprungen werden. Bei der assert-Anweisung handelt es sich um ein weiterführendes Konzept, das seit dem JDK 1.4 zur Verfügung steht. Neben einem grundlegenden Verständnis von Klassen und Methoden (ab Kapitel 7) setzt es insbesondere Kenntnisse über das Auslösen und Behandeln von Ausnahmen voraus, die erst in Kapitel 12 vermittelt werden. Aus systematischen Gründen soll das Thema jedoch an dieser Stelle behandelt werden.

 Hinweis 

Syntax

assert ausdruck1 [ : ausdruck2 ] ;

Bedeutung

Seit dem JDK 1.4 gibt es in Java eine assert-Anweisung. Diese, ursprünglich schon für die erste JDK-Version vorgesehene, dann aber aus Zeitgründen zurückgestellte Anweisung wird C- oder C++-Programmierern wohlbekannt sein. Sie dient dazu, an einer bestimmten Stelle im Programmablauf einen logischen Ausdruck zu plazieren, von dem der Programmierer annimmt, daß er stets wahr ist. Ist das der Fall, fährt das Programm mit der nächsten Anweisung fort, andernfalls wird eine Ausnahme des Typs AssertionError ausgelöst. Ein solcher Ausdruck wird auch als Invariante bezeichnet, denn er bleibt unverändert true, solange das Programm korrekt funktioniert.

 JDK1.1-1.4 

Assertions, wie assert-Anweisungen vereinfachend genannt werden, dienen also dazu, bestimmte Annahmen über den Zustand des Programmes zu verifizieren und sicherzustellen, daß diese eingehalten werden. Soll im Programm beispielsweise überprüft werden, ob eine Variable x nicht-negativ ist, könnte dazu die folgende assert-Anweisung verwendet werden:

assert x >= 0;

Das Programm überprüft die Bedingung und fährt fort, wenn sie erfüllt ist. Andernfalls wird eine Ausnahme ausgelöst. Natürlich hätte derselbe Test auch mit Hilfe einer einfachen if-Anweisung ausgeführt werden können. Die assert-Anweisung hat ihr gegenüber jedoch einige Vorteile:

Der im Syntaxdiagramm angegebene ausdruck1 muß immer vom Typ boolean sein, andernfalls gibt es einen Compilerfehler. Fehlt ausdruck2 (er darf von beliebigem Typ sein), wird im Falle des Nichterfülltseins der Bedingung ein AssertionError mit einer leeren Fehlermeldung erzeugt. Wurde ausdruck2 dagegen angegeben, wird er im Fehlerfall an den Konstruktor von AssertionError übergeben und dient als Meldungstext für das Fehlerobjekt.

An- und Abschalten der Assertions

Aus Kompatibilität zu älteren JDK-Versionen sind Assertions sowohl im Compiler als auch im Interpreter standardmäßig deaktiviert. Um einen Quellcode zu übersetzen, der Assertions enthält, kann dem Java-Compiler ab der Version 1.4 die Option -source 1.4 übergeben werden. Wird dies nicht gemacht, gibt es eine Fehlermeldung, nach der "assert" ab der Version 1.4 ein Schlüsselwort ist und nicht mehr als Bezeichner verwendet werden darf. Ältere Compilerversionen melden einen Syntaxfehler. Wir wollen uns ein einfaches Beispielprogramm ansehen:

001 /* AssertionTest.java */
002 
003 public class AssertionTest
004 {
005   public static void main(String[] args)
006   {
007     assert args.length >= 2; 
008     int i1 = Integer.parseInt(args[0]);
009     int i2 = Integer.parseInt(args[1]);
010     assert i2 != 0 : "Teilen durch 0 nicht moeglich"; 
011     System.out.println(i1 + "/" + i2 + "=" + (i1/i2));
012   }
013 }
AssertionTest.java
Listing 6.7: Anwendung von Assertions

Das Beispielprogramm verwendet zwei Assertions, um sicherzustellen, daß mindestens zwei Kommandozeilenargumente übergeben werden und daß das zweite von ihnen nicht 0 ist. Es kann mit folgendem Kommando übersetzt werden, wenn der Compiler mindestens die Version 1.4 hat:

javac -source 1.4 AssertionTest.java

Um das Programm laufen zu lassen, kennt der Java-Interpreter ab der Version 1.4 die Kommandozeilenoptionen -enableassertions und -disableassertions, die mit -ea bzw. -da abgekürzt werden können. Ihre Syntax lautet:

java [ -enableassertions | -ea  ] [:PaketName... | :KlassenName ]

java [ -disableassertions | -da  ] [:PaketName... | :KlassenName ]

Werden die Optionen ohne nachfolgenden Paket- oder Klassennamen angegeben, werden die Assertions für alle Klassen mit Ausnahme der Systemklassen java.* an- bzw. ausgeschaltet. Wird, durch einen Doppelpunkt getrennt, ein Klassenname angegeben, gilt die jeweilige Option nur für diese Klasse. Wird ein Paketname angegeben (von einem Klassennamen durch drei angehängte Punkte zu unterscheiden), erstreckt sich die Option auf alle Klassen innerhalb des angegebenen Pakets. Die Optionen können mehrfach angegeben werden, sie werden der Reihe nach ausgewertet. Wird keine dieser Optionen angegeben, sind die Assertions deaktiviert.

Soll unser Beispielprogramm mit aktivierten Assertions ausgeführt werden, kann also folgendes Kommando verwendet werden:

java -ea AssertionTest

In diesem Fall gibt es sofort eine Fehlermeldung, denn die erste assert-Anweisung ist nicht erfüllt. Rufen wir das Programm mit zwei Zahlen als Argumente auf, wird erwartungsgemäß deren Quotient berechnet:

java -ea AssertionTest 100 25

Die Ausgabe lautet:

100/25=4

Wenn das zweite Argument dagegen 0 ist, gibt es eine Fehlermeldung, weil die zweite Assertion nicht erfüllt ist. Auch in diesem Fall steigt das Programm mit einem AssertionError aus, der zusätzlich die Fehlermeldung »Teilen durch 0 nicht moeglich« enthält, die wir nach dem Doppelpunkt in Zeile 010 angegeben haben:

Exception in thread "main" java.lang.AssertionError:
  Teilen durch 0 nicht moeglich
...

Wird das Programm mit deaktivierten Assertions aufgerufen, verhält es sich, als wären die Zeilen 007 und 010 gar nicht vorhanden. In diesem Fall gibt es die üblichen Laufzeitfehler, die bei einem Zugriff auf ein nicht vorhandenes Array-Element oder die Division durch 0 entstehen.

Anwendungen

Genau genommen war das vorige Programm kein wirklich gutes Beispiel für die Anwendung von Assertions. Das Überprüfen der an ein Programm übergebenen Kommandozeilenparameter sollte nämlich besser einer IllegalArgumentException überlassen werden:

001 public class Listing0608
002 {
003   public static void main(String[] args)
004   {
005     if (args.length < 2) {
006       throw new IllegalArgumentException();
007     }
008     ...
009   }
010 }
Listing 6.8: Verwendung einer IllegalArgumentException

Genau wie bei der Übergabe eines Arguments an eine öffentliche Methode sollte es nicht einfach möglich sein, deren Überprüfung zur Laufzeit abzuschalten. Da ein Programm bei der Übergabe von Werten an öffentliche Schnittstellen keinerlei Annahmen darüber machen kann, ob diese Werte korrekt sind, sollte die Überprüfung dieser Werte immer aktiv sein. Die Verwendung von Assertions empfiehlt sich also in diesem Fall nicht. Weitere Beispiele für derartige öffentliche Schnittstellen sind etwa die Daten, die über eine grafische Benutzerschnittstelle in ein Programm gelangen oder die aus Dateien oder über Netzwerkverbindungen eingelesen werden. In all diesen Fällen sollten nicht-abschaltbare Fehlerprüfungen anstelle von Assertions verwendet werden.

Der Einsatz von Assertions ist dagegen immer dann sinnvoll, wenn Daten aus unbekannten Quellen bereits verifiziert sind. Wenn also das Nichterfülltsein einer Assertion einen Programmfehler anzeigt und nicht fehlerhafte Eingabedaten. Beispiele sind:

Sowohl Pre- als auch Postconditions wurden mit der Programmiersprache Eiffel, die Bertrand Meyer in seinem Buch »Object-oriented Software Construction« 1988 vorgestellt hat, einer breiteren Öffentlichkeit bekannt. Das im JDK 1.4 implementierte Assertion-Konzept entspricht allerdings nicht in allen Aspekten Meyer's ausgefeiltem »Programming by Contract«. Dort gibt es beispielsweise explizite Schlüsselwörter für Pre- und Postconditions, und es ist möglich, in Postconditions auf den »alten« Wert eines Parameters zuzugreifen (also auf den, den er beim Eintritt in die Methode hatte).

 Hinweis 

Dennoch stellen Assertions ein wichtiges Hilfsmittel dar, um Programme zuverlässiger und besser lesbar zu machen. Das folgende Programm zeigt verschiedene Anwendungen von Assertions am Beispiel einer einfachen Klasse zur Speicherung von Listen von Ganzzahlen:

001 public class SimpleIntList
002 {
003   private int[] data;
004   private int   len;
005 
006   public SimpleIntList(int size)
007   {
008     this.data = new int[size];
009     this.len  = 0;
010   }
011 
012   public void add(int value)
013   {
014     //Precondition als RuntimeException
015     if (full()) { 
016       throw new RuntimeException("Liste voll");
017     }
018     //Implementierung
019     data[len++] = value;
020     //Postcondition
021     assert !empty(); 
022   }
023 
024   public void bubblesort()
025   {
026     if (!empty()) {
027       int cnt = 0;
028       while (true) {
029         //Schleifeninvariante
030         assert cnt++ < len: "Zu viele Iterationen"; 
031         //Implementierung...
032         boolean sorted = true;
033         for (int i = 1; i < len; ++i) {
034           if (sortTwoElements(i - 1, i)) {
035             sorted = false;
036           }
037         }
038         if (sorted) {
039           break;
040         }
041       }
042     }
043   }
044 
045   public boolean empty()
046   {
047     return len <= 0;
048   }
049 
050   public boolean full()
051   {
052     return len >= data.length;
053   }
054 
055   private boolean sortTwoElements(int pos1, int pos2)
056   {
057     //Private Preconditions
058     assert (pos1 >= 0 && pos1 < len); 
059     assert (pos2 >= 0 && pos2 < len); 
060     //Implementierung...
061     boolean ret = false;
062     if (data[pos1] > data[pos2]) {
063       int tmp = data[pos1];
064       data[pos1] = data[pos2];
065       data[pos2] = tmp;
066       ret = true;
067     }
068     //Postcondition
069     assert data[pos1] <= data[pos2] : "Sortierfehler"; 
070     return ret;
071   }
072 }
SimpleIntList.java
Listing 6.9: Anwendung von Assertions

Precondition-Assertions sind in den Zeilen 058 und 059 zu sehen. Sie stellen sicher, daß die Methode mit gültigen Array-Indizes aufgerufen wird. Eine Precondition, die als RuntimeException implementiert wurde, findet sich in Zeile 015 und prüft, ob die Liste vor dem Einfügen eines weiteren Elements bereits voll ist. Während die Postcondition in Zeile 021 eher formaler Natur ist, überprüft jene in Zeile 069, ob das Sortieren der Elemente tatsächlich erfolgreich war. Die Schleifeninvariante in Zeile 030 stellt sicher, daß der Bubblesort nicht zu viele Arraydurchläufe macht, was ein Indiz dafür wäre, daß er in eine Endlosschleife geraten wäre.

Nebeneffekte

Normalerweise sollten die Ausdrücke in Assertions keine Nebeneffekte enthalten. Also keinen Code, der Variablen verändert oder den Status des Programmes auf andere Weise modifiziert. Der Grund dafür ist, daß diese Nebeneffekte bei abgeschalteten Assertions natürlich nicht ausgeführt werden. Das Programm würde sich also anders verhalten, als wenn die Assertions angeschaltet wären. Dadurch können sehr schwer zu findende Fehler entstehen. Generell gilt, daß die Funktionalität des Programms nicht davon abhängen sollte, ob die Assertions an- oder abgeschaltet sind.

 Warnung 

Allerdings kann es berechtigte Ausnahmen geben. Ein Gegenbeispiel liefert etwa das vorige Listing in Zeile 030. Hier wird innerhalb der Assertion die Zählervariable cnt inkrementiert, also eine Variable verändert. Das ist allerdings unkritisch, denn diese wird auch nur innerhalb der Assertion benötigt, spielt ansonsten im Programm aber keine Rolle. Variablen, die auch an anderen Stellen im Programm verwendet werden, sollten allerdings nicht innerhalb von Assertions verändert werden.

Kompatibilität

Wie schon erwähnt, gibt es Assertions erst seit der Version 1.4 des JDK. Ihre Verwendung wirft leider einige Kompatibilitätsprobleme auf:

Was folgt daraus? Assertions lassen sich nur dann sinnvoll einsetzen, wenn der Entwickler einen JDK-1.4.Compiler besitzt und davon ausgehen kann, daß auf allen Zielsystemen ein 1.4-kompatibles Laufzeitsystem vorhanden ist. Letzteres ist - zumindest zum Zeitpunkt, an dem diese Zeilen geschrieben werden - leider nicht unbedingt der Fall. Der Reiz von Java liegt ja gerade in seiner Plattformunabhängigkeit, und bis auf allen wichtigen Zielsystemen ein JDK-1.4-Laufzeitsystem zur Verfügung stehen wird, könnte es noch einige Zeit dauern. Wegen der zur Installation eines neueren JDKs erforderlichen Betriebssystempatches auf UNIX-Systemen sind heute - mehr als zwei Jahre nach der Veröffentlichung des JDK 1.3 - immer noch viele UNIX-Systeme lediglich mit einem 1.2er JDK ausgestattet oder wurden gerade erst auf 1.3 umgestellt. Vermutlich wird es noch einige Zeit dauern, bis das JDK 1.4 flächendeckend verfügbar sein wird.

Fazit: Assertions sind ein ausgezeichnetes Mittel, um Code lesbarer und robuster zu gestalten. Während der Debugphase helfen sie bei der Fehlersuche. Sofern keine Kompatibilität zu älteren JDK-Versionen erforderlich ist, sollten sie daher unbedingt verwendet werden. Kann dagegen nicht sichergestellt werden, daß die Zielsysteme mindestens das JDK 1.4 unterstützen, können Assertions nicht verwendet werden.


 Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0.1
 <<    <     >    >>   API  © 1998-2003 Guido Krüger, http://www.javabuch.de