8.1.3 Version 2: zwei add()-Methoden
Wir erweitern die Klasse MyArrayList jetzt um zwei add()-Methoden, die ein neues Objekt
an das Ende des Arrays anhängen bzw.
an einer bestimmten Position einfügen.
Wie bei einer echten ArrayList soll das interne Array dann auch pseudo-dynamisch wachsen können.
"Pseudo-dynamisch" heißt: Es wird einfach ein größeres Array erzeugt, alle Elemente des bisherigen Arrays werden in das neue Array kopiert, und die alte Referenz-Variable, die auf das alte Array gezeigt hat, wird auf das neue Array "umgebogen".
Version 2.1: Die erste add()-Methode: anhängen
Die add()-Methode
public void add(Object element)
{
if (element == null)
throw new IllegalArgumentException("Element ist null.");
if (size >= elementData.length)
grow();
elementData[size] = element;
size++;
}
Quelltext: MyArrayList (Version 2.1)
Bei der add()-Methode wird eine IllegalArgumentException geworfen, und zwar dann, wenn das übergebene Objekt den Wert null hat.
Bevor das Objekt der Liste hinzugefügt wird, wird erst einmal überprüft, ob die Größe der Liste (size) größer oder gleich der Kapazität (length) ist. Wenn das der Fall ist, wird grow() aufgerufen. Anschließend wird das neue Objekt an das Ende der bisherigen Liste eingefügt.
Warum keine NullPointerException ?
Eine NullPointerException wird hier nicht verwendet, obwohl der fehlerhafte Wert ja durchaus null ist. Etwas anderes wäre es, wenn man in der add()-Methode tatsächlich auf Methoden des Objekts element zugreifen wollte, beispielsweise so:
String text = element.toString();
Dann würde nämlich bei einem null-Wert automatisch eine NullPointerException auftreten. In unserer add()-Methode greifen wir jedoch gar nicht direkt auf das Objekt element zu, sondern kopieren lediglich eine Referenz auf element in das entsprechende Arrayelement elementData[size].
Die grow()-Methode
private void grow()
{
int alteKapazitaet = elementData.length;
int neueKapazitaet = alteKapazitaet + alteKapazitaet / 2;
if (neueKapazitaet == alteKapazitaet)
neueKapazitaet = alteKapazitaet + 1;
Object[] neuesArray = new Object[neueKapazitaet];
for (int i=0; i < alteKapazitaet; i++)
neuesArray[i] = elementData[i];
elementData = neuesArray;
// elementData = Arrays.copyOf(elementData, neueKapazitaet);
}
Quelltext: MyArrayList (Version 2.1)
Die Methode grow() berechnet zunächst die neue Kapazität aus der bisherigen Kapazität. Bei einer Liste der Länge 0 oder 1 würde die Multiplikation der alten Kapazität mit dem Faktor 1,5 zu einer neuen Kapazität von 0 oder 1 führen; die Kapazität würde also nicht wachsen. Aus diesem Grund wird in diesem Fall die alte Kapazität einfach um 1 erhöht.
Fallbeispiel
Angenommen, die alte Kapazität hätte den Wert 1. Mit der Formel
neueKapazitaet = alteKapazitaet + alteKapazitaet / 2;
käme man auf eine neue Kapazität von 1.5, die dann aber als Ganzzahl 1 interpretiert würde. Das heißt, die neue Kapazität ist nicht größer als die alte. Daher wird in diesem Fall einfach eine 1 dazu addiert, so dass man auf eine neue Kapazität von 2 kommt.
Die Kopieraktion
Zunächst wird ein neues Array mit der größeren Kapazität angelegt, dann werden in der for-Schleife alle Elemente aus dem alten Array in das neue Array kopiert, und schließlich wird die Referenz elementData, die bisher auf das alte Array verwiesen hat, auf das größere Array "umgebogen".
Eine einzeilige Alternative zu diesem Vierzeiler ist als Kommentar ebenfalls angegeben. Wenn man die copy()-Methode der Klasse Arrays benutzen will, muss man diese Klasse aber zuvor über die Importanweisung einbinden:
import java.util.Arrays;
Version 2.1: Testprogramm
Für ein Testprogramm wäre es sinnvoll, wenn wir auch die aktuelle Größe (size) sowie die aktuelle Kapazität (length) der Liste überprüfen könnten. Daher ergänzen wir die Klasse MyArrayList noch kurz um zwei Getter-Methoden für diese Attribute:
public int size()
{
return size;
}
public int capacity()
{
return elementData.length;
}
Hier werden keine Parameter übergeben, somit können auch keine falschen Argumente übergeben werden. Das heißt, auf das Prüfen bzw. Werfen von einer IllegalArgumentException können wir hier verzichten.
Wir wollen die neue Methode add() nun testen. In dem Methoden der Testklasse werden wir Exception-Handling mit dem try-catch-Konstrukt anwenden, denn die add()-Methode kann ja durchaus eine IllegalArgumentException werfen.
Quelltext des Testprogramms
public class Test
{
private MyArrayList wortliste;
public Test()
{
try
{
wortliste = new MyArrayList(5);
fuegeObjekteEin();
}
catch (IllegalArgumentException ausnahme)
{
System.out.println("Fehler beim Erzeugen von wortliste: " +
ausnahme.getMessage());
}
}
private void fuegeEinObjektEin(Object neu)
{
System.out.println("Einfügen des Objektes " + neu);
try
{
wortliste.add(neu);
System.out.printf("Neues Objekt %-20s", neu);
System.out.printf("size = %3d, length = %3d %n%n",
wortliste.size(),wortliste.capacity());
}
catch (IllegalArgumentException ausnahme)
{
System.out.println
("Fehler bei add(): " + ausnahme.getMessage() + "\n");
}
}
public void fuegeObjekteEin()
{
fuegeEinObjektEin(" 1. Name");
fuegeEinObjektEin(" 2. Vorname");
fuegeEinObjektEin(" 3. Straße");
fuegeEinObjektEin(null);
fuegeEinObjektEin(" 4. Hausnummer");
fuegeEinObjektEin(" 5. Postleitzahl");
fuegeEinObjektEin(23);
fuegeEinObjektEin(true);
fuegeEinObjektEin(" 6. Wohnort");
fuegeEinObjektEin(" 7. Landkreis");
fuegeEinObjektEin(" 8. Bundesland");
fuegeEinObjektEin(" 9. Land");
fuegeEinObjektEin("10. Kontinent");
fuegeEinObjektEin("11. Planet");
}
public static void main(String[] args)
{
Test test = new Test();
System.out.println("Das Programm wird bis zum Ende ausgeführt!");
}
}
Quelltext: Test (Version 2.1)
Die Testklasse enthält drei wichtige Methoden:
1. Konstruktor
Hier wird versucht, ein Objekt wortliste der Klasse MyArrayList zu erzeugen. Die IllegalArgumentException, die vom Konstruktor von MyArrayList geworfen werden kann, wird über eine try-catch-Konstruktion abgefangen.
2. fuegeEinObjektEin(Object neu)
Es wird versucht, das Objekt neu in die Liste einzufügen. Zur Kontrolle und besseren Übersicht wird dann das Objekt mit dem println()-Befehl in der Konsole angezeigt. Außerdem - wieder zur Kontrolle - wird die aktuelle Größe (size) sowie die aktuelle Kapazität (capacity) der Liste ausgegeben.
3. fuegeObjekteEin()
Mit dieser Methode werden jetzt verschiedene Objekte in die Liste eingefügt, indem die private Methode fuegeEinObjektEin() aufgerufen wird. Einige dieser Objekte sind String-Objekte und sollten daher keine Ausnahme verursachen. Einmal wird aber null übergeben, was eine Ausnahme provozieren soll.
Bei der Übergabe von 23 und true sollten keine Fehler entstehen, denn diese beiden primitiven Datentypen werden von den neueren Java-Versionen automatisch in Objekte der Wrapper-Klassen Integer und Boolean umgewandelt und können daher problemlos in die Liste eingefügt werden (Autoboxing).
Ausgabe des Testprogramms
Einfügen des Objektes 1. Name Neues Objekt 1. Name size = 1, length = 5 Einfügen des Objektes 2. Vorname Neues Objekt 2. Vorname size = 2, length = 5 Einfügen des Objektes 3. Straße Neues Objekt 3. Straße size = 3, length = 5 Einfügen des Objektes null Fehler bei add(): Element ist null. Einfügen des Objektes 4. Hausnummer Neues Objekt 4. Hausnummer size = 4, length = 5 Einfügen des Objektes 5. Postleitzahl Neues Objekt 5. Postleitzahl size = 5, length = 5 Einfügen des Objektes 23 Neues Objekt 23 size = 6, length = 7 Einfügen des Objektes true Neues Objekt true size = 7, length = 7 Einfügen des Objektes 6. Wohnort Neues Objekt 6. Wohnort size = 8, length = 10 Einfügen des Objektes 7. Landkreis Neues Objekt 7. Landkreis size = 9, length = 10 Einfügen des Objektes 8. Bundesland Neues Objekt 8. Bundesland size = 10, length = 10 Einfügen des Objektes 9. Land Neues Objekt 9. Land size = 11, length = 15 Einfügen des Objektes 10. Kontinent Neues Objekt 10. Kontinent size = 12, length = 15 Einfügen des Objektes 11. Planet Neues Objekt 11. Planet size = 13, length = 15 Das Programm wird bis zum Ende ausgeführt!
Beim Versuch, das "Objekt" null einzufügen, wird die selbst erstellte Fehlermeldung ausgegeben, die teils im try-Block erzeugt wurde, teils dem Konstruktor der IllegalArgumentException als Parameter mitgegeben wurde. Die "Objekte" 23 und true werden wie erwartet problemlos in die Liste eingefügt, nämlich als Objekte der entsprechenden Wrapper-Klassen Integer und Boolean.
Was man gut erkennen kann, ist das pseudo-dynamische Wachstum der Liste. Gestartet wird mit einer Kapazität von 5, die dann nacheinander auf 7, 10 und 15 erhöht wird.
Version 2.2: Beschränkung auf "echte" Objekte
Wir wollen im nächsten Schritt die add()-Methode so verändern, dass keine primitiven Datentypen wie int, double oder boolean mehr in der Liste gespeichert werden können, sondern nur "echte" Objekte - also auch keine Integer-, Double- oder Boolean-Objekte.
Dazu setzen wir den instanceof-Operator ein:
public void add(Object element)
{
if (element == null)
throw new IllegalArgumentException("Element ist null.");
if (element instanceof Integer ||
element instanceof Double ||
element instanceof Boolean)
throw new IllegalArgumentException
("int-, double- oder boolean-Werte sind nicht erlaubt.");
if (size >= elementData.length)
grow();
elementData[size] = element;
size++;
}
Quelltext: MyArrayList (Version 2.2), Test (Version 2.2)
In der Konsolenausgabe erscheinen dann entsprechende Fehlermeldungen, wenn wir die "Objekte" 23 und true in die Liste einfügen wollen:
Einfügen des Objektes 23 Fehler add(): int-, double- oder boolean-Werte sind nicht erlaubt. Einfügen des Objektes true Fehler add(): int-, double- oder boolean-Werte sind nicht erlaubt.
Version 2.3: Die add(index)-Methode
Wie erweitern die Klasse MyArrayList jetzt um eine zweite add()-Methode, bei der die Position des einzufügenden Elements bestimmt werden kann:
public void add(int index, Object element)
// Neu in Version 2.3
{
if (element == null)
throw new NullPointerException("element ist null.");
if (element instanceof Integer ||
element instanceof Double ||
element instanceof Boolean)
throw new IllegalArgumentException
("int-, double- oder boolean-Werte sind nicht erlaubt.");
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("index ausserhalb: " + index);
if (size >= elementData.length)
grow();
for (int i = size; i > index; i--)
elementData[i] = elementData[i - 1];
elementData[index] = element;
size++;
}
Quelltext: MyArrayList (Version 2.3)
In dieser Version der add()-Methode begegnen wir gleich drei verschiedenen Ausnahmen.
Zunächst prüfen wir, ob das neue Element den Wert null hat und werfen in diesem Fall eine NullPointerException.
Als Zweites wird überprüft, ob das übergebene Objekt ein "echtes" Objekt ist, also kein Integer-, Double- oder Boolean-Objekt. Sollte das doch der Fall sein, wird eine IllegalArgumentException geworfen.
Schließlich wird überprüft, ob der übergebene Index innerhalb der erlaubten Werte liegt. Als Obergrenze wird hier aber nicht elementData.length verwendet, sondern size. Diese add()-Methode darf das neue Objekt also nur in den belegten Teil der Liste einfügen oder direkt an das Ende der Liste anhängen (index == size). Wenn diese Bedingungen nicht erfüllt sind, wird eine IndexOutOfBoundsException geworfen.
Zur IndexOutOfBoundsException
Autor: Ulrich Helmich 05/2026, Lizenz: Public domain
Ein Einfügen "irgendwo" in den freien Bereich am Ende des internen Arrays ist bewusst nicht erlaubt, weil dadurch Lücken entstehen würden und size nicht mehr zur tatsächlichen Belegung passen würde.
Bevor nun das neue Element an der entsprechenden Position eingefügt wird, rücken die weiter rechts stehenden Elemente je eine Position weiter nach rechts. Dann wird das neue Element eingefügt und size inkrementiert.
Version 2.3: Das Testprogramm
Wir ergänzen das Testprogramm aus der Version 2.2 um eine weitere Methode:
private void fuegeEinObjektEin(int index, Object neu)
{
System.out.println("Einfügen des Objektes " + neu + " an Position " + index);
try
{
wortliste.add(index,neu);
System.out.printf("Neues Objekt %-20s", neu);
System.out.printf("size = %3d, length = %3d %n%n",
wortliste.size(),wortliste.capacity());
}
catch (NullPointerException ausnahme)
{
System.out.println
("Fehler bei add(index): " + ausnahme.getMessage() + "\n");
}
catch (IllegalArgumentException ausnahme)
{
System.out.println
("Fehler bei add(index): " + ausnahme.getMessage() + "\n");
}
catch (IndexOutOfBoundsException ausnahme)
{
System.out.println
("Fehler bei add(index): " + ausnahme.getMessage() + "\n");
}
}
Quelltext: Test (Version 2.3)
Diese Methode soll die zweite add()-Methode testen. Für jede der drei möglichen Exceptions wurde hier ein eigener catch-Block implementiert worden. So könnte man theoretisch sehr differenziert auf die verschiedenen Ausnahmen eingehen, was wir hier aber noch nicht machen.
Bei allen drei Exceptions erscheint in der Konsole die gleiche Meldung "Fehler bei add(index): ". Das ist noch nicht sehr spezifisch. Aber dann wird die Methode getMessage() des Exception-Objektes ausnahme aufgerufen, und diese Methode liefert die spezifische Fehlermeldung der jeweiligen Exception.
Man könnte sich die Sache aber auch etwas einfacher machen, indem man nur einen einzigen catch-Block implementiert, in dem alle Unterklassen von Exception abgefangen werden:
catch (Exception ausnahme)
{
System.out.println
("Fehler bei add(index): " + ausnahme() + "\n");
}
Exception ist die Oberklasse aller Exception-Klassen, und dieser catch-Block fängt nun alle Unterklassen von Exception auf, also auch die NullPointerException, die IllegalArgumentException und die IndexOutOfBoundsException.
Sollte aus irgendeinem Grund eine andere Exception von add(index) geworfen werden, würde auch diese vom catch-Block aufgefangen.
Seitenanfang -
Weiter mit dem Prinzip der Redundanz-Vermeidung ...