PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Java - Hilfe bei Generics


Sephiroth
2006-07-12, 18:18:56
Ich habe Generics in Java bisher immer nur bei ArrayList, TreeMap und Konsorten genutzt aber noch nie selbst eine Klasse damit geschrieben.

Klappt das so hier?
Insbesondere der erste Konstruktor! Wenn man
Replikat<Integer> = new Replikat<Integer>();
benutzt, dann soll der konstruktor im Prinzip
this.object = new Integer();
machen. Würde er das?

class Replikat<E> implements java.io.Serializable
{
private E object;
private String version;

//@SuppressWarnings("unchecked")
public Replikat()
{
this.object = (E) new Object();
this.version = "1.0";
}
public Replikat(E initValue)
{
this.object = initValue;
this.version = "1.0";
}

public E getObject()
{
return this.object;
}

public String getVersion()
{
return this.version;
}

// setzt neuen wert und erhöht versionsnummer und gibt diese zurück
public String setValue(E newValue)
{
this.object = newValue;
double oldVersion = Double.valueOf(this.version);
this.version = String.valueOf(oldVersion + 0.1);
return this.version;
}

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException
{
// Write out version
s.writeUTF(version);

// Write the element itself
s.writeObject(object);
}
//@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException
{
// Read in array length and allocate array
this.version = s.readUTF();

// Read the element itself
this.object = (E) s.readObject();
}
}

Xmas
2006-07-12, 18:32:26
this.object = new E();

PH4Real
2006-07-12, 18:44:23
Öhm... das wird beides nicht gehen... bin mir ziemlich sicher, dass man so eine generizedParameter nicht instanzieren kann.

EDIT: Einfach aus dem Grund, da nicht alle Objekte mittels new Object(); instanziert werden können. Änder einfach deinen Konstruktor insowas um:


public Replicate(E object) {
this.object = object;
this.version = "1.0";
}


EDIT2: Ahh... Du hast ja sowas schon... der Default-Konstruktor muss einfach weg ;)

Sephiroth
2006-07-12, 18:53:02
Xmas[/POST]']this.object = new E(); Das hab ich natürlich schon probiert. ;)

Replikat.java:25: unexpected type
found : type parameter E
required: class
this.object = new E();
___________^
1 error

Öhm... das wird beides nicht gehen... bin mir ziemlich sicher, dass man so eine generizedParameter nicht instanzieren kann.

EDIT: Einfach aus dem Grund, da nicht alle Objekte mittels new Object(); instanziert werden können. Änder einfach deinen Default-Konstruktor insowas um:
hm, gibt es da echt nichts für die faulen Leute? :(

PH4Real
2006-07-12, 18:59:03
Sephiroth[/POST]']

hm, gibt es da echt nichts für die faulen Leute? :(

Hmm... denke nicht. Nicht jedes Objekt kann ja einfach so instanziert werden. Du könntest ja auch Interfaces oder abstrakte Klassen als Type Parameter übergeben wollen.

PS: Dein Surpress Warnings kannst du ruhig lassen. Die Warnung besagt nur, dass wenn Du als "E" z.b. eine "ArrayList<String>" hast, dass er dann nur auf die ArrayList ansich beim casten prüfen kann, jedoch nicht auf den parametrisierten Typ.

Sephiroth
2006-07-12, 19:02:50
klingt plausibel :-/

Und wie stets um writeObject() und readObject() für's Serialisieren? Muß/sollte ich da noch defaultWriteObject() bzw. defaultReadObject() als erstes aufrufen?

Ich habe gerade mir RMI zu schaffen und damit da etwas übergeben werden kann, muß es serialisierbar sein.

Monger
2006-07-12, 19:57:06
Bei den Generics muss man sich ein paar Sachen klarmachen.

Generics sind nicht so generisch wie sie sich anhören. Sie existieren nicht zur Runtime, sie existieren nichtmal zur Compiletime. Sie sind nichts anderes als ein Compiler Hack um bequem und sicher casten zu können.

Generics mit Casts zu mischen, macht fast nie einen Sinn. Die Generics sollen Casts ja eben (so gut es geht) überflüssig machen. Überall da wo Objekte neu enstehen (z.B. aus irgendwelchen Datenströmen) haben die Generics aber auch ihre Grenzen. Spätestens da muss man casten.

Da Generics nicht zur Runtime existieren, gibt es auch keine generischen Objekte. Der Compiler kann allenfalls vorher checken, ob ein Objekt das rein geht auch mit dem was rausgeht typmäßig übereinstimmt.

Du kannst also Generics deklarieren, aber nicht instanziieren. Ein Generic heißt nix anderes als "dieses Ding und jenes besitzen den selben Typ".


Ich hab selber mal ein bißchen mit Generics rumgebastelt, und hab festgestellt, dass sie eigentlich fast nie Sinn machen - außer in Collections. Man kann auch sehr leicht übertypisieren.

stefan42
2006-07-12, 20:40:09
Du könntest dem Constructor als Parameter eine Klasse mitgeben. Dann weißt zu Du auch zur Laufzeit, mit welchem Typ parametrisiert wurde und Du kannst sogar Arrays von diesem Typ anlegen oder per newInstance versuchen neue Objekte anzulegen.

public class Generics<T> {

private Class<T> parameterClazz;

public Generics(Class<T> clazz)
{
this.parameterClazz = clazz;
}

public T createNewInstance() throws InstantiationException, IllegalAccessException
{
return parameterClazz.newInstance();
}
...
}

Sephiroth
2006-07-12, 21:33:17
Und wie sähe dann ein Aufruf des Konstruktors mit der Angabe der Klasse aus?

Gast
2006-07-13, 08:48:51
Generics<Date> d = new Generics<Date>(Date.class);

Gewinnt keinen Schönheitspreis, tut aber das, was Generics eigentlich ohnehin tun sollten (nämlich die Klasse des Typparameters irgendwo speichern).

Gast
2006-07-13, 08:56:08
Noch eine kleine Erweiterung. Es gibt viele gute Gründe statische Factorymethoden Konstruktoren vorzuziehen. Damit lässt sich folgendes bauen:

...
public static <T> Generics<T> create(Class<T> clazz)
{
return new Generics<T>(clazz);
}
...
Generics<Date> d2 = Generics.create(Date.class);


Hier wird der Typparameter implizit aus dem Parameter ermittelt, so dass man auf die Doppelung Typparameter und Klasse verzichten kann.

HellHorse
2006-07-13, 18:10:51
Sephiroth[/POST]']
hm, gibt es da echt nichts für die faulen Leute? :(
Statisches Typsystem geht nicht zusammen mit faule Leute.
PH4Real[/POST]']
PS: Dein Surpress Warnings kannst du ruhig lassen. Die Warnung besagt nur, dass wenn Du als "E" z.b. eine "ArrayList<String>" hast, dass er dann nur auf die ArrayList ansich beim casten prüfen kann, jedoch nicht auf den parametrisierten Typ.
Ruhig? Auf keinen Fall. Der Unchecked cast sagt ja, dass dort zu Laufzeit kein cast sein wird. Falls das Objekt den falschen Typ hat, wirst du es sehr viel später merken. Damit führst du das ganze statische Typsystem ad absurdum.


import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class Replikat<T> implements Serializable {

private static final long serialVersionUID = 5588362299766733849L;

private Class<T> parameterClazz;

private T object;

private String version;

public Replikat(Class<T> clazz) throws InstantiationException, IllegalAccessException {
this.parameterClazz = clazz;
this.object = this.parameterClazz.newInstance();
this.version = "1.0";
}

public Replikat(T initialValue, Class<T> clazz) {
this.parameterClazz = clazz;
this.object = initialValue;
this.version = "1.0";
}

public T getObject() {
return this.object;
}

public String getVersion() {
return this.version;
}

/**
* setzt neuen wert und erhöht versionsnummer und gibt diese zurück
*/
public String setValue(T newValue) {
this.object = newValue;
double oldVersion = Double.valueOf(this.version);
this.version = String.valueOf(oldVersion + 0.1);
return this.version;
}

private void writeObject(ObjectOutputStream stream) throws IOException {
// Write out version
stream.writeUTF(this.version);

// Write the element itself
stream.writeObject(this.object);
}

private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException {
// Read in array length and allocate array
this.version = stream.readUTF();

// Read the element itself
this.object = this.parameterClazz.cast(stream.readObject());

Dieser Code hat natürlich das Problem, dass die Klasse nicht (de)serialisiert wird. Diese liesse sich auch nicht gescheit casten. Soll nur ein Beispiel sein, wie man mit generics "richtig" castet.

#writeObject und #readObject kannst du dir eigentlich schenken. Wird automatisch so gemacht womit dann auch das class cast Problem gelöst wäre.

Wie üblich gilt:
RTFM (http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html)


Es gibt viele gute Gründe statische Factorymethoden Konstruktoren vorzuziehen.
Dummerweise lassen sich statische Factorymethoden genauso wenig in ein Interface packen wie Konstruktoren :frown:

PH4Real
2006-07-13, 19:06:27
HellHorse[/POST]']
Ruhig? Auf keinen Fall. Der Unchecked cast sagt ja, dass dort zu Laufzeit kein cast sein wird. Falls das Objekt den falschen Typ hat, wirst du es sehr viel später merken. Damit führst du das ganze statische Typsystem ad absurdum.


Ja das stimmt.. besser ist es gleich zu überprüfen, ob denn da das Objekt drin ist, welches ich auch haben möchte. Aber mit den Generics in Java hat man da sowieso seine liebe Not, wenn die serialisiert werden sollen, da dann Typ Information ja weg sind :(.

Ach ja, sogar jetzt wird bestimmt Eclipse noch rummeckern, wegen dem Unboxing von Double in "setValue" ;)...

Sephiroth
2006-07-13, 22:37:14
Danke für eure Hilfe. :)

Hab jetzt noch "clazz" mit drinne und auch serialVersionUID (;)).

Ach ja, sogar jetzt wird bestimmt Eclipse noch rummeckern, wegen dem Unboxing von Double in "setValue" ...
Ich nutze SciTe ;D