Vektoren
Arrays haben verschiedene, sehr vorteilhafte Eigenschaften: Sie sind schnell, da beim Zugriff auf Elemente im Array auf Methodenaufrufe (die immer etwas Zeit kosten) verzichtet werden kann, und sie nehmen relativ wenig Speicherplatz in Anspruch. Allerdings haben Arrays immer eine feste Größe - wenn diese nicht bekannt ist, so muss dann, wenn ein Array vergrößert werden soll, ein neues Array erzeugt werden, in welches die Elemente des alten Arrays kopiert werden usw. U.a. aus diesem Grund existiert die Klasse "Vector" in Java.
Die Klasse Vector
Ein Vector ist ein "normales" Objekt, welches wie üblich mit "new Vector()" erzeugt wird, und das Methoden wie get(), add(), size(), clear() u.v.m. anbietet, um den Umgang mit einer Liste so einfach wie möglich zu gestalten.
Vector vector = new Vector(5);
vector.add("Hallo"); // Hinzufügen eines Objekts am Ende der Liste
vector.add("Holla");
System.out.println(vector.size());
System.out.println(vector.get(0)); // Ausgabe des ersten Elements
vector.remove(1); // Löschen des Elements an Position 1
Prinzipiell kann in einem Vector jedes Objekt gespeichert werden - wie ist dies möglich? Sie haben gelernt, dass die Argumente von Methoden immer typisiert sein müssen, d.h. dass angegeben werden muss, von welchem Typ ein Argument sein darf - die Klasse Vector kann aber unmöglich eine add()-Methode für jedes denkbare Objekt mit sich bringen! 1 Ein längerer Exkurs: Das Substitutionsprinzip, die Klasse Object, Casting und der instanceof-Operator Die Lösung des oben geschilderten Problems liegt in der Vererbung: Die Signatur der add()-Methode zeigt, dass ein Objekt der Klasse Object erwartet wird:
public void add(Object element) ...
D.h. die add-Methode akzeptiert jedes Objekt, das eine Instanz von Object ist - oder eine Instanz einer von Object abgeleiteten Klasse ( lässt sich ja auch interpretieren als "die Klasse X ist auch Y"). Wenn also z.B. die Klasse String von Object abgeleitet wäre, so ließe sich problemlos ein String-Objekt als Parameter für die add-Methode nutzen. Ein Nachteil würde jedoch bleiben: Jede Klasse müsste irgendwie von Object abgeleitet werden, dies wäre aber umständlich, würde vermutlich häufig vergessen etc. Daher wird diese Ableitung "von Java" automatisch durchgeführt: Wenn in der Definition einer Klasse nicht explizit angegeben wird, dass sie aus einer anderen Klasse erben soll, so wird die Klasse automatisch von Object abgeleitet. Somit ist jede Klasse in Java immer auch ein Object, und jede Instanz jeder Klasse kann als Parameter für Methoden dienen, die Instanzen der Klasse Object erwarten.
Vector vector = new Vector();
vector.add(5);
vector.add("Hallo");
vector.add(new Hund());
Die zugrundeliegende Idee der Object-Klasse basiert auf dem sogenannten Substitutionsprinzip Allerdings ergibt sich ein neues Problem: Was, wenn nun mit Hilfe der get()-Methode ein Element aus dem Vektor zurückgegeben werden soll - welchen Typ hat dieses Element dann? Die Antwort ist etwas unbefriedigend: Es hat den Typ Object - damit kann man jedoch nicht viel anfangen, es sollte schließlich wieder vom Typ Integer, String oder Hund sein bzw. als solches interpretiert werden können, denn der Typ des Objekts hat sich ja nicht tatsächlich verändert, der Compiler hat lediglich "vergessen", dass es sich nicht nur um eine Instanz von Object, sondern auch von Integer, String oder Hund handelt. Für dieses Problem gibt es zwei Lösungen: Eine "klassische" Methode, die seit der ersten Version von Java zur Verfügung steht (Casting), und eine "moderne" Methode, die erst mit Java 5 Einzug in die Sprache gefunden hat (Java Generics). Aufgrund unglücklicher technischer Umstände (während der Java-Übung ist Java 5 nicht verfügbar) sowie aufgrund der Tatsache, dass es sich bei Generics letztlich auch nur um Casting handelt, wird hier Casting verwendet, während Generics erst im 2. Semester behandelt werden.
Casting
Casting ist sozusagen die Umkehrung des Substitutionsprinzips: Während letzteres besagt, dass eine Methode
public void methode(Object object) {
}
auch mit einer Variablen aus einer von Object abgeleiteten Klasse arbeiten kann, ermöglicht es Casting, diesen Vorgang wieder umzukehren.
Vector vector = new Vector();
vector.add("Hallo"); // String einfügen
Object object = vector.get(0); // Rückgabe des Strings,
// der jetzt als Object interpretiert wird
String string = (String) object; // Casting auf den "richtigen" Typ.
Syntaktisch funktioniert dies so, dass auf der rechten Seite einer Zuweisung innerhalb von runden Klammern der Name der Klasse, auf die gecastet werden soll, angegeben wird (s.o.). Casting ist jedoch keine Zauberei, d.h. es ist nicht möglich, aus einem String mittels Casting einen Integer zu machen o.ä. Vielmehr ist Casting immer nur auf dem direkten Pfad zwischen Object und der "eigentlichen" Klasse möglich:
Angenommen, eine Instanz von Nacktmull wäre als Object obj referenziert, so ließen sich folgende Castings ausführen:
Lebewesen l = (Lebewesen) obj;
Tier t = (Tier) obj;
Nacktmull n = (Nacktmull) obj;
Folgende Castings würden hingegen nicht funktionieren:
Pflanze p = (Pflanze) obj;
Integer i = (Integer) obj;
usw.
Diese Fehler werden jedoch nicht vom Compiler erkannt, denn das Object obj ja durchaus vom Typ Pflanze oder Integer sein - erst zur Laufzeit des Programms stellt sich heraus, dass sich in obj ein Nacktmull verbirgt, der inkompatibel zu Pflanze ist. Da das Programm in diesem Fall nicht weiterarbeiten kann, wird eine Fehlermeldung "ClassCastException" produziert und das Programm abgebrochen.
instanceof
Damit wären die Besonderheiten für den Umgang mit Object-Objekten auch fast schon besprochen, gäbe es nicht noch ein Problem: Weiter oben wurden in einem Beispiel Objekte vom Typ Integer, String und Hund in einem Vector abgelegt - wie kann nun getestet werden, um welche Art von Objekt es sich handelt, wenn ein Element mittels get() aus dem Vector geholt wird? Zunächst sei bemerkt, dass es i.d.R. schlechter Stil ist, zueinander inkompatible Objekte in einer gemeinsamen Datenstruktur zu speichern, da in der weiteren Verwendung Fallunterscheidungen verursacht werden, die evtl. vermieden werden könnten. Trotzdem kann ein solches Vorgehen u. U. sinnvoll oder unvermeidbar sein, und in diesen Fällen kann mit Hilfe des Operators instanceof eine Fallunterscheidung durchgeführt werden:
Object obj = vector.get(index);
if(obj instanceof Integer) {
?
} else if (obj instanceof String) {
?
}
...
Der instanceof-Operator verknüpft eine Variable mit einer Klasse und bildet diese auf einen booleschen Wert ab: true, wenn die Variable ein Objekt der angegebenen Klasse repräsentiert, sonst false.