8.1.1 Zielsetzung
In den letzten Folgen (Arrays, Sortieralgorithmen und Suchverfahren) haben wir viele Methoden zum Umgang mit Arrays kennengelernt.
Und in der letzten Folge (ArrayList) haben wir uns dann mit der Sammlungs-Klasse ArrayList beschäftigt und dabei auch schon einen kurzen Blick auf das Thema Generics geworfen.
In dieser Folge verfolgen wir mehrere Ziele gleichzeitig:
- Erstellung einer Klasse MyArrayList, die ähnlich arbeiten soll wie die Klasse ArrayList.
- Die Behandlung von Exceptions (Ausnahmen) mit throws und try-catch.
- Vertiefung des Thema generische Datentypen.
8.1.2 Basis-Version von MyArrayList
Beginnen wir mit der Basis-Version der Klasse. Es soll ein Objekt-Array beliebiger Länge erzeugt werden können. Wir übergeben dem Konstruktor die Startkapazität als Parameter. Ähnlich wie bei einer echten ArrayList soll die Kapazität dann pseudo-dynamisch erhöht werden können, wenn entsprechender Bedarf besteht.
Version 1a: Konventionelle Fehlermeldung
Die ArrayList-Klasse
public class MyArrayListSimple
{
private Object[] elementData;
private int size;
public MyArrayListSimple(int startCapacity)
// Haupt-Konstruktor
{
if (startCapacity <= 0)
{
System.out.println("Ungültige Startkapazität");
return;
}
elementData = new Object[startCapacity];
size = 0;
}
public MyArrayListSimple()
// Komfort-Konstruktor
{
this(10);
}
}
Quelltext: MyArrayListSimple
Instanzvariablen
- elementData: das interne Array, das Referenzen auf gespeicherte Objekte enthält.
- size: die Anzahl der tatsächlich belegten Elemente. Ähnlich wie bei einer echten ArrayList darf hier size nicht mit length verwechselt werden.
Die Namen dieser beiden Instanzvariablen wurden absichtlich so gewählt, dass sie mit den Instanzvariablen der Klasse ArrayList übereinstimmen.
Konstruktoren
- Der Haupt-Konstruktor erstellt das interne Array mit der angegebenen Startkapazität, wenn diese mindestens 1 beträgt. Andernfalls wird eine Fehlermeldung auf der Konsole ausgegeben und der Konstruktor terminiert.
- Der Komfort-Konstruktor benötigt keine Angabe der Startkapazität, er ruft den Haupt-Konstruktor mit dem Wert 10 auf, sodass ein Array mit length = 10 und size = 0 erstellt wird.
Die Test-Klasse
public class TestSimple
{
// eine fehlerhafte (F) und eine korrekte (K) Wortliste
private MyArrayListSimple wortlisteF, wortlisteK;
public TestSimple()
{
wortlisteF = new MyArrayListSimple(-3);
if (wortlisteF == null)
System.out.println("wortlisteF existiert nicht!");
else
System.out.println("wortlisteF -3: " + wortlisteF);
wortlisteK = new MyArrayListSimple(4);
if (wortlisteK == null)
System.out.println("wortlisteF existiert nicht!");
else
System.out.println("wortlisteF +4: " + wortlisteK);
}
public static void main(String[] args)
{
TestSimple test = new TestSimple();
System.out.println("Das Programm wird bis zum Ende ausgeführt!");
}
}
Quelltext: TestSimple
Diese Testklasse überprüft nun die Arbeitsweise der ersten Basisversion unserer ArrayList. Zunächst wird bewusst versucht, eine fehlerhafte Liste mit einer Startkapazität von -3 anzulegen. Dann wird überprüft, ob die Erzeugung eines solchen Objekts gelingt. Wenn das Objekt wortlisteF nicht erzeugt werden kann, wird die Meldung "wortlisteF existiert nicht" in der Konsole ausgegeben. Andernfalls wird die Adresse des Objektes angezeigt.
Anschließend wird eine zweite Wortliste wortlisteK mit einer gültigen Startkapazität von 4 angelegt. Sollte dies nicht gelingen, wird eine entsprechende Meldung angezeigt. Bei Erfolg wird die Adresse des Objektes ausgegeben.
Konsolenausgabe der Testklasse
Ungültige Startkapazität wortlisteF -3: MyArrayListSimple@57469ffb wortlisteK +4: MyArrayListSimple@5120125d Das Programm wird bis zum Ende ausgeführt!
Der Versuch, eine ungültige Liste wortlisteF anzulegen, wird zunächst mit einer Fehlermeldung quittiert: "Ungueltige Startkapazität". Es wird aber trotzdem ein Objekt angelegt, wie die zweite Zeile der Konsolenausgabe zeigt. Allerdings handelt es sich hier um ein fehlerhaftes Objekt, mit dem man nicht arbeiten kann und das bei einem "echten" Programm gravierende Probleme bereiten könnte.
Die zweite Wortliste wortlisteK wird ebenfalls erzeugt, hat allerdings eine Größe von 0, sodass keine weiteren Operationen getestet werden können.
Version 1b: Exception statt Fehlermeldung
Die Array-Klasse
Der folgende Quelltext zeigt einen überarbeiteten Konstruktor der Klasse MyArrayList:
public MyArrayList(int startKapazitaet)
{
if (startKapazitaet <= 0)
throw new IllegalArgumentException
("Ungueltige Startkapazitaet: " + startKapazitaet);
elementData = new Object[startKapazitaet];
size = 0;
}
Quelltext: MyArrayList
Bevor wir jetzt auf Exceptions (Ausnahmen) und die Behandlung solcher Exceptions eingehen, schauen wir uns das entsprechende Testprogramm sowie die Konsolenausgabe dieses Testprogramms näher an.
Das Testprogramm
public class TestBetter
{
private MyArrayList wortlisteF, wortlisteK;
public TestBetter()
{
try
{
wortlisteF = new MyArrayList(-3);
if (wortlisteF == null)
System.out.println("wortlisteF existiert nicht!");
else
System.out.println("wortlisteF -3: " + wortlisteF);
wortlisteK = new MyArrayList(4);
if (wortlisteK == null)
System.out.println("wortlisteK existiert nicht!");
else
System.out.println("wortlisteK +4: " + wortlisteK);
}
catch (IllegalArgumentException e)
{
System.out.println("Fehler beim Erzeugen von wortliste: " + e.getMessage());
}
}
public static void main(String[] args)
{
TestBetter test = new TestBetter();
System.out.println("Das Programm wird bis zum Ende ausgeführt!");
}
}
Quelltext: TestBetter
Die Konsolenausgabe
Fehler beim Erzeugen von wortliste: Ungueltige Startkapazitaet: -3 Das Programm wird bis zum Ende ausgeführt!
Das ist eine sehr kurze Konsolenausgabe. Die Fehlermeldung, die durch die falsche Startkapazität verursacht wird, erscheint noch in der Konsole. Dann werden alle anderen Anweisungen des Testprogramms übersprungen, und in der zweiten Zeile wird der letzte Befehl der main()-Methode ausgeführt.
Der genaue Ablauf des Testprogramms
- Der Haupt-Konstruktor von MyArrayList wird gestartet
- Die Bedingung (startKapazitaet <= 0) ist wahr.
- Die Exception wird mit throw ausgelöst.
- Der Konstruktor wird sofort abgebrochen.
- Das Objekt wortlisteF wird nicht erzeugt.
- Die Programmausführung springt sofort in den catch-Block.
- Alle nachfolgenden Anweisungen im try-Block werden übersprungen.
Am wichtigsten ist hier die Tatsache, dass die Objekterzeugung durch die throw-Anweisung sofort abgebrochen wird (Schritt 4). Daher kann kein fehlerhaftes MyArrayList-Objekt erzeugt werden, wie es bei Version 1a (normale Fehlermeldung statt Exception) noch möglich war.
Merke:
Das ist der entscheidende Vorteil: Das Programm arbeitet nicht mit einem beschädigten Objekt weiter, das später Probleme verursachen könnte. Stattdessen wird der Fehler sofort an der Stelle sichtbar, an der er entsteht.
Fehlerbehandlung mit try-catch
Wenn in einem Java-Programm eine Exception auftritt, wird die normale Programmausführung unterbrochen (auch ohne return-Anweisung!). Ohne besondere Maßnahmen würde das Programm dann sofort mit einer Fehlermeldung abbrechen.
Mit Hilfe eines try-catch-Konstrukts können solche Exceptions jedoch gezielt abgefangen und behandelt werden.
Allgemeine Syntax
try
{
// Anweisungen, die eine Exception auslösen können
}
catch (Exceptiontyp e)
{
// Behandlung des Fehlers
}
Der try-Block enthält also Anweisungen, bei denen eventuell eine Exception auftreten kann (aber nicht muss). Tritt innerhalb dieses Blocks keine Exception auf, werden alle Anweisungen normal ausgeführt, und der catch-Block wird dann übersprungen.
Ablauf beim Auftreten einer Exception
Wird dagegen eine passende Exception ausgelöst, geschieht Folgendes:
- Die normale Programmausführung wird sofort unterbrochen, dabei werden
- alle restlichen Anweisungen des try-Blocks übersprungen und
- die Programmausführung springt direkt in den catch-Block.
- Dort kann der Fehler dann behandelt werden.
Betrachten wir dazu noch einmal das try-catch-Konstrukt unseres zweiten Testprogramms:
try
{
wortlisteF = new MyArrayList(-3);
if (wortlisteF == null)
System.out.println("wortlisteF existiert nicht!");
else
System.out.println("wortlisteF -3: " + wortlisteF);
wortlisteK = new MyArrayList(4);
if (wortlisteK == null)
System.out.println("wortlisteK existiert nicht!");
else
System.out.println("wortlisteK +4: " + wortlisteK);
}
catch (IllegalArgumentException e)
{
System.out.println("Fehler beim Erzeugen von wortliste: " + e.getMessage());
}
Ablauf im try-Block
Beim Aufruf
wortlisteF = new MyArrayList(-3);
tritt im Konstruktor der Klasse MyArrayList eine IllegalArgumentException auf. Diese Exception wird durch die throw-Anweisung ausgelöst:
throw new IllegalArgumentException
("Ungueltige Startkapazitaet:" + startKapazitaet);
Dadurch wird der Konstruktor sofort beendet. Das Objekt wird nicht erzeugt, und die Programmausführung springt direkt in den catch-Block der Testklasse. Die im try-Block folgenden Anweisungen
if (wortlisteF == null)
System.out.println("worlisteF existiert nicht!");
else
System.out.println("wortlisteF -3: " + wortlisteF);
// etc.
werden daher nicht mehr ausgeführt.
Ablauf im catch-Block
catch (IllegalArgumentException e)
{
System.out.println("Fehler beim Erzeugen von wortliste: " + e.getMessage());
}
Im catch-Block steht die Variable e für das Exception-Objekt. Über Methoden dieses Objekts können weitere Informationen über den Fehler abgefragt werden. Besonders wichtig ist die Methode getMessage(). Diese Methode liefert genau den Text zurück, den wir beim Erzeugen der Exception im Konstruktor von MyArrayList angegeben haben:
throw new IllegalArgumentException
("Ungueltige Startkapazitaet:" + startKapazitaet);
In der Konsole erscheinen nun zwei Fehlermeldungen:
Fehler beim Erzeugen von wortliste: Ungueltige Startkapazitaet: -3
Die erste Meldung wird von dem System.out.println()-Befehl des catch-Blocks erzeugt. Die zweite Meldung dagegen ist das Ergebnis von e.getMessage().
Seitenanfang -
Weiter mit den beiden add()-Methoden ...