From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+
+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.
+
+
+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 platzieren,
+von dem der Programmierer annimmt, dass 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.
+Assertions, wie assert-Anweisungen
+vereinfachend genannt werden, dienen also dazu, bestimmte Annahmen
+über den Zustand des Programms zu verifizieren und sicherzustellen,
+dass 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:
+
+
+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
+muss 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.
+
+
+
+
+
+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
+(ab der J2SE 5.0 auch -source 1.5)
+übergeben werden. Wird dies nicht gemacht, gibt es eine Fehlermeldung,
+nach der »assert« ein Schlüsselwort ist und nicht mehr
+als Bezeichner verwendet werden darf. Ältere Compilerversionen
+melden einen Syntaxfehler. Wir wollen uns ein einfaches Beispielprogramm
+ansehen:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 6 - Anweisungen
+
+
+
+
+
+6.4 Sonstige Anweisungen
+
+
+
+
+
+
+
+6.4.1 Die assert-Anweisung
+
+
+
+
+
+
+
+
+
+
+![]()
+
+
+
+![]()
+
+
+
+
+
+ Hinweis
+
+
Syntax
+
+
+
+
+
+
+
+
+
+
+
+
+assert ausdruck1 [ : ausdruck2 ] ;
+
+
+Bedeutung
+
+
+
+
+
+
+
+![]()
+
+![]()
+
+
+
+![]()
+
+
+
+
+
+ JDK1.1-6.0
+
+
+assert x >= 0;
+
+
+
+
+
+
+An- und Abschalten der Assertions
+
+
+
+
+Listing 6.9: Anwendung von Assertions
+
+
+
+
+
+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
+
+Das Beispielprogramm verwendet zwei Assertions, um sicherzustellen,
+dass mindestens zwei Kommandozeilenargumente übergeben werden
+und dass 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. + + + + +
+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 Listing0610
+002 {
+003 public static void main(String[] args)
+004 {
+005 if (args.length < 2) {
+006 throw new IllegalArgumentException();
+007 }
+008 ...
+009 }
+010 }
+
+ |
+
+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). |
+
+
|
+![]() |
+
+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 | +
+Precondition-Assertions sind in den Zeilen 058 +und 059 zu sehen. Sie stellen +sicher, dass 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, dass der Bubblesort nicht zu viele Arraydurchläufe +macht, was ein Indiz dafür wäre, dass er in eine Endlosschleife +geraten wäre. + + + + +
+
![]() |
+![]() |
+
+
+ +Normalerweise sollten die Ausdrücke in Assertions keine Nebeneffekte +enthalten. Also keinen Code, der Variablen verändert oder den +Status des Programms auf andere Weise modifiziert. Der Grund dafür +ist, dass 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, +dass die Funktionalität des Programms nicht davon abhängen +sollte, ob die Assertions an- oder abgeschaltet sind. |
+
+
|
+![]() |
+
+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. + + + + +
+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 mindestens einen JDK-1.4.Compiler besitzt und +davon ausgehen kann, dass auf allen Zielsystemen, für die er +entwickelt, ein mindestens 1.4-kompatibles Laufzeitsystem vorhanden +ist. Letzteres ist mitunter 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 und auch installiert ist, könnte es noch einige Zeit +dauern. Wegen der zur Installation eines neueren JDKs oft erforderlichen +Betriebssystempatches auf UNIX- oder Host-Systemen sind noch immer +viele derartige Systeme mit älteren JDKs ausgestattet. + +
+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, dass die Zielsysteme +mindestens das JDK 1.4 unterstützen, können Assertions nicht +verwendet werden. +
| Titel + | Inhalt + | Suchen + | Index + | DOC + | Handbuch der Java-Programmierung, 5. Auflage, Addison +Wesley, Version 5.0.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |