Seite 1 von 2

Ex 7.4 Generics

Verfasst: 10. Dez 2007 18:06
von ArVee
Hallo,

die NumberVector Klasse auf Generics umzustellen sah nicht so schwer aus, doch jetzt bekomme ich im Testcase ClassCastExceptions, die gar nicht auftreten dürften ;)

Aus dem Testcase:

Code: Alles auswählen

final NumberVector<Number> v = new NumberVector<Number>(12);
v.add(1);
v.add(2);
assertEquals(2, v.size());

final Number[] array = v.toArray();
[...]
Die Zeile mit v.toArray wirft die Exception.

Aber v.toArray sollte T[] zurückliefern:

Code: Alles auswählen

public T[] toArray() {
    return Arrays.copyOf(this.elementdata, this.size);
}
elementdata ist ein Array vom Typ T[] und wird mit einem Trick erstellt, den ich unter Generic Arrays gefunden hatte.

Kann mir da jemand weiterhelfen?

Oder soll man beim NumberVector intern kein Array mehr verwenden?

Verfasst: 10. Dez 2007 21:32
von es11
Hi,
ich hab mich auch gefragt ob wir das überhaupt mit Arrays machen sollen, weil dieses workaround mit dem generic Array ist schon etwas komisch :)

Aber hab dann mal in die jdk geschaut, wie die selbst Vector<E> gemacht haben:

Vector.java
version 1.106, 06/16/06

Code: Alles auswählen

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

.
.
    
protected Object[] elementData;

public Vector(int initialCapacity, int capacityIncrement) {
	super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);

	this.elementData = new Object[initialCapacity];
	this.capacityIncrement = capacityIncrement;
}

public synchronized E get(int index) {
	if (index >= elementCount)
	    throw new ArrayIndexOutOfBoundsException(index);

	return (E)elementData[index];
}
}


Also scheinst du mit dem Array auf dem richtigen weg zu sein ;)

Verfasst: 11. Dez 2007 10:47
von IchWarsNicht
Ok -.-

Ich geb mal nen gute Tip weiter...

Code: Alles auswählen

T[] elementdata = (T[]) Array.newInstance(Number.class, initialCapacity);

Verfasst: 11. Dez 2007 11:10
von marcel_b
Hi,

die Erzeugung von Arrays mit der oben beschriebenen Lösung bringt Laufzeitfehler (selbst mit Number.class).

Damit das nicht in allzuviel Arbeit ausartet:Es gibt keine saubere Lösung um generische Arrays zu erzeugen.

Wenn dem so wäre, gäbe es vermutlich nicht die (nicht generische) Methode
Object[] List.toArray(), die ein Object[] zurückliefert - vollkommen egal ob die Liste parametrisiert ist oder nicht...

Zumindest könnt ihr jedoch innerhalb eines gewissen Rahmens gewährleisten, dass alles so laufen wird, wie man es erwartet. Zieht in Betracht die Signature einer Methode oder des Constructors zu ändern und schaut euch die Methoden der Klasse java.lang.reflect.Array an (wie oben im Tip beschrieben; nur Number.class ist nicht ausreichend).

Verfasst: 11. Dez 2007 14:36
von es11
Schade das es keine saubere Lösung gibt.

Code: Alles auswählen

    public Vector(XXXXXX, final int initialCapacity) {
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("<0");
        }
        this.elementdata = (T[]) Array.newInstance(XXXXXXXX, initialCapacity); 
    }
Habt ihr irgendwie an sowas gedacht? Oder ist das schon zu unsauber? (funktioniert tut es)

// EDIT Marcel: Das war ein bisschen viel Lösung... Das muss als Hilfestellung reichen, oder?

Verfasst: 11. Dez 2007 14:40
von marcel_b
ja, an sowas hätte ich jetzt auch gedacht... Zumindest ist es das eleganteste, das mir zu diesem Problem einfällt.

Aber das muss als Hilfestellung reichen, oder?

Verfasst: 11. Dez 2007 17:46
von Ultr1
Die Methoden sollen den Clients ja den Subtyp von Number zurückliefern, so dass der Client nicht jedes Ergebnis dieser Methoden von Number wieder runtercasten muss.
marcel_b hat geschrieben: Es gibt keine saubere Lösung um generische Arrays zu erzeugen.
Ich nehme nun mal an, dass also das Array elementsdata weiterhin vom Supertyp Number sein soll. Das würde aber implizieren, dass zb bei einer getter-Methode für ein Array Element nun von einer Number zum generischen Subtyp T gecastet werden muss. Diesen Subtyp verlangt ja der Client.
Jetzt habe ich hier natürlich einen Typcast, bei dem mich schon Eclipse warnt:
(T) NumberVector.this.elementdata[this.pos++];
Type safety: Unchecked cast from number to T.
Da T jedoch laut generischer Signatur ein Subtyp sein muss sollte dieser Cast immer Klappen, sofern der Anwender auch einen Subtyp von Number übergibt.

Meine Frage ist jetzt: Sollen wir hier eine Ausnahmebehandlung für falsche Parametertypen machen (zb bei der add Methode abfangen, dass der Benutzer keinen falschen Typ addet)? Oder fehlt mir einfach eine Idee um dann Runtime Errors zu vermeiden? Lässt man jedenfalls das Array ungenrisch, so taucht dieses Problem definitiv auf.

Verfasst: 11. Dez 2007 19:33
von marcel_b
>>
Ich nehme nun mal an, dass also das Array elementsdata weiterhin vom Supertyp Number sein soll
<<

Nein. Wie in der Aufgabestellung und in der Übung gesagt, soll eure Lösung ohne Objekte/Arrays vom Typ Number auskommen. Abgesehen davon wirft die toArray() Methode dann eine ClassCastException.

Falsche Parameter kann es nicht geben, da alle Methoden mit dem generischen Parameter T parametrisiert sind.

Verfasst: 11. Dez 2007 19:53
von Evgeni
marcel_b hat geschrieben:>>
Ich nehme nun mal an, dass also das Array elementsdata weiterhin vom Supertyp Number sein soll
<<

Nein. Wie in der Aufgabestellung und in der Übung gesagt, soll eure Lösung ohne Objekte/Arrays vom Typ Number auskommen. Abgesehen davon wirft die toArray() Methode dann eine ClassCastException.

Falsche Parameter kann es nicht geben, da alle Methoden mit dem generischen Parameter T parametrisiert sind.
Reicht es wenn man in Array Elemente eines Typs hinzufügen kann, oder sollte auch sowas gehen:
X.add(1);
X.add(1.0d);
....

Verfasst: 12. Dez 2007 08:52
von marcel_b
Letzteres. Alles was in den generischen Paramter T reinpasst - Wenn T= Number, dann Float, Double, Integer usw. Wenn T = Integer, dann nur Integer oder Subtypen davon (wenn Integer welche hätte).

Verfasst: 12. Dez 2007 23:07
von MuldeR
Hallo,

wir scheitern leider die ganze Zeit an dieser Zeile:
this.elementdata = (T[]) Array.newInstance(XXXXXXXX, initialCapacity);

mit folgender Zeile tut es natürlich, solange man <T> mit Number instanziiert:
this.elementdata = (T[]) Array.newInstance(Number.class, initialCapacity);

aber dann könnte man genau so gut gleich das hier machen:
this.elementdata = (T[]) Number[initialCapacity];

Irgendwie muss man es also in der Art hin bekommen, damit es generisch wird:
this.elementdata = (T[]) Array.newInstance(T.class, initialCapacity);

leider geht das so nicht und durch rumprobieren kommen wir hier leider auch nicht weiter :?

Hat jemand noch en Tipp, wie man das hinbekommt und on man es überhaupt mit dem newInstance() hinbekommen kann...

Verfasst: 13. Dez 2007 08:41
von marcel_b
ja, man bekommt es über newInstance() hin.

Schaut euch die Klasse java.lang.Class an. Auch Class kann parametrisiert werden: Class<T>

Verfasst: 13. Dez 2007 17:09
von MuldeR
marcel_b hat geschrieben:ja, man bekommt es über newInstance() hin.

Schaut euch die Klasse java.lang.Class an. Auch Class kann parametrisiert werden: Class<T>
Okay, soweit sind wir ja jetzt.
Die frage ist, wie ich meinem formalen Parameter T ein Class<T> entlocken kann, um es dem newInstance zu übergeben...

Wir haben das jetzt nach ewig langem rumprobieren so gemacht:

Code: Alles auswählen

public class GenericVector<T> implements Iterable<T> {

  ...

  @SuppressWarnings("unchecked")
  public GenericVector(final T dummyParam, final int initialCapacity) {

    ...

    this.elementdata = (T[]) Array.newInstance(dummyParam.getClass(), initialCapacity);
  }

  ...

}

Code: Alles auswählen

  @Test
  public void toArray() throws Exception {
    final GenericVector<Number> v = new GenericVector<Number>(new Integer(0), 12);

    ...
  
  }
Ist das so im Rahmen der angedachten Lösung? Immerhin scheint es zu funktionieren ......

Verfasst: 13. Dez 2007 17:13
von marcel_b
Fast. Es braucht jedoch kein Dummyobjekt. Wieso willst du dummyParam ein Class<T> entlocken, wenn du Class<T> übergeben kannst...?

Verfasst: 13. Dez 2007 17:29
von MuldeR
marcel_b hat geschrieben:Fast. Es braucht jedoch kein Dummyobjekt. Wieso willst du dummyParam ein Class<T> entlocken, wenn du Class<T> übergeben kannst...?
Okay, immerhin :)

Beim meinem Dummy-Object vom Type T kann ich halt sicher sein, dass es wirklich vom Typ T ist.
Wenn ich ein (beliebiges) Class<T> Object übergeben lasse, könnte mir der Client doch einfach einen falschen Class-Typ unterjubeln.....


// EDIT

Okay, hab gerade gemerkt, dass man es doch hinbekommen kann. Und zwar so, dass der Typ stimmt ^^