8.1.5 MyArrayList wird generisch
Einleitung und Begründung
In den bisherigen Versionen unserer Klasse MyArrayList konnten wir beliebige Objekte im internen Array elementData speichern. Das war möglich, weil dieses Array als Object-Array angelegt war. Auf diese Weise konnten in einer einzigen Liste beispielsweise String-Objekte, Name-Objekte oder auch Punkt-Objekte gemeinsam gespeichert werden. Eine solche Liste nennt man heterogen.
Dieses Vorgehen hatte jedoch auch Nachteile. Da in einer Liste Objekte unterschiedlicher Klassen liegen können, ist beim Auslesen eines Elements zunächst unklar, welchen Typ es hat. Dies erschwert die Arbeit mit der Liste und erhöht die Fehleranfälligkeit. Typumwandlungen sind häufig erforderlich, und genau dabei können Laufzeitfehler auftreten.
Aus diesem Grund werden wir MyArrayList jetzt generisch machen.
Eine generische Klasse wird nicht mehr für beliebige Objekte formuliert, sondern für Objekte eines bestimmten Typs. Man kann dann zum Beispiel mit
MyArrayList<String> wortliste = new MyArrayList<>();
eine Liste für Wörter anlegen, in der nur String-Objekte gespeichert werden können.
Mit
MyArrayList<Punkt> punktliste = new MyArrayList<>();
kann man entsprechend eine Liste anlegen, in der nur Objekte der Klasse Punkt gespeichert werden können.
Da der Typ der gespeicherten Elemente von Anfang an eindeutig festgelegt ist, kann der Compiler bereits beim Übersetzen des Programms viele Fehler erkennen. Bei einer Anweisung wie
punktliste.add("Mein Name");
würde der Compiler die Übersetzung mit einer Fehlermeldung abbrechen, weil in die Liste punktliste ein Objekt vom Typ String eingefügt werden soll, obwohl dort nur Punkt-Objekte erlaubt sind.
Der Quelltext der generischen Klasse, Teil 1
Betrachten wir nun den Anfang des modifizierten Quelltextes der Klasse MyArrayList. Im Vergleich zur bisherigen Version wurde die Klasse MyArrayList an den entscheidenden Stellen so umgebaut, dass sie generisch ist.
Das Ziel dieser Änderung ist, dass ein Objekt der Klasse MyArrayList nicht mehr beliebige Objekte speichern soll, sondern nur noch Objekte eines vorher festgelegten Typs.
import java.util.Arrays; public class MyArrayList{ // Instanzvariablen // ========================================================== private T[] elementData; // wurde angepasst; T = Typ-Parameter private int size; // Zwei Konstruktoren // ========================================================== public MyArrayList(int startKapazitaet) { if (startKapazitaet <= 0) throw new IllegalArgumentException ("Ungueltige Startkapazitaet: " + startKapazitaet); elementData = (T[]) new Object[startKapazitaet]; size = 0; }
Eigentlich wurde der Quelltext nur an drei Stellen verändert. Wir wollen diese Änderungen nun genauer betrachten.
1. Änderungen in der Kopfzeile
In der Kopfzeile der Klasse steht jetzt
public class MyArrayList <T>
Dabei ist <T> der sogenannte Typ-Parameter der Klasse. Ein solcher Typ-Parameter steht als Platzhalter für einen konkreten Datentyp, der erst beim Erstellen eines MyArrayList-Objekts festgelegt wird. Wenn wir später zum Beispiel schreiben
MyArrayList<String> wortliste = new MyArrayList<>();
dann steht T in dieser Liste für String. Bei
MyArrayList<Punkt> punktliste = new MyArrayList<>();
steht T entsprechend für Punkt.
2. Änderungen an elementData
In der bisherigen Version von MyArrayList war die Instanzvariable elementData so deklariert:
private Object[] elementData;
Statt dessen schreiben wir jetzt:
private T[] elementData;
Auch das ist eine wichtige Änderung. Das interne Array soll nun nicht mehr allgemein ein Object-Array sein, sondern ein Array für Elemente des Typs T.
Wenn T also String ist, dann wird elementData wie ein String-Array behandelt. Wenn T dagegen Punkt ist, ist elementData quasi ein Punkt-Array.
3. Änderungen im Konstruktor
Im Konstruktor steht jetzt:
elementData = (T[]) new Object[startKapazitaet];
Das sieht zunächst vielleicht etwas merkwürdig aus. Warum wird hier nicht einfach
elementData = new T[startKapazitaet];
geschrieben?
Der Grund ist: In Java kann man kein Array eines Typ-Parameters direkt erzeugen. Eine Anweisung wie
new T[startKapazitaet];
ist nicht erlaubt. Deshalb wird zunächst ein Object-Array erzeugt:
new Object[startKapazitaet]
und dieses Array wird dann mit ([T]) in ein Array vom Typ T[] umgewandelt.
Der Quelltext der generischen Klasse, Teil 2
Ein Großteil der Methoden von MyArrayList muss nicht verändert werden, beispielsweise die Methode size() oder die Methode checkElementIndex(). Eine Methode, die angepasst werden musste, ist checkObject().
Nicht-generische Version:
private void checkObject(Object element) // wurde angepasst
{
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.");
}
Generische Version:
private void checkObject(T element) // wurde angepasst
{
if (element == null)
throw new IllegalArgumentException
("Element ist null.");
}
Wenn wir beispielsweise ein Objekt des Typs MyArrayList<String> anlegen, dann verhindert bereits der Compiler, dass eine int- oder Integer-Variable eingefügt wird. Eine zusätzliche Laufzeitprüfung auf Integer, Double oder Boolean ist dann überflüssig.
Die Prüfung auf null ist dagegen weiterhin sinnvoll, wenn null-Werte in der Liste grundsätzlich nicht als Elemente erlaubt sein sollen - ausgenommen sind hier natürlich die null-Werte, die beim Erzeugen oder Vergrößern der Liste automatisch angelegt werden.
Bei den folgenden Methoden von MyArrayList muss lediglich das Wort Object durch den Buchstaben T ersetzt werden:
- public void add(Object element)
- public void add(int index, Object element)
- public Object get(int index)
- public void set(int index, Object element)
- public boolean contains(Object element)
- public int indexOf(Object element)
Die Methode set() wurde beispielsweise wie folgt verändert:
Nicht-generische Version:
public void set(int index, Object element)
{
checkObject(element);
checkElementIndex(index);
elementData[index] = element;
}
Generische Version:
public void set(int index, T element)
{
checkObject(element);
checkElementIndex(index);
elementData[index] = element;
}
Den kompletten Quelltext dieser generischen Klasse können Sie sich hier herunter laden!
Übung
Laden Sie den Quelltext MyArrayList.java zu ChatGPT oder einer anderen KI hoch und lassen Sie sich dann eine Testklasse dazu erzeugen.
Geben Sie der KI an, dass die Testklasse alle Methoden von MyArrayList gründlich testen soll - auch die Handhabung der Exceptions soll getestet werden - dazu müssen natürlich bewusst solche Fehler in die Testklasse eingebaut werden.
Wenn Sie keinen Zugang zu einer KI haben sollten, können Sie sich auch entsprechende Klassen von dieser Homepage herunter laden:
- TestMyArrayList.java (die Testklasse)
- Name.java
- Punkt.java
Die beiden letzten Klassen werden von der Testklasse benötigt, damit die Verwendung des Typ-Parameters getestet werden kann.
Seitenanfang -
Weiter mit Folge 8.2 - Was sind Exceptions?