Problem mit java.lang.reflect.Field

  • Themenstarter Themenstarter Guvnor
  • Beginndatum Beginndatum
Status
Nicht offen für weitere Antworten.
G

Guvnor

Gast
Hallo!

Ich versuche gerade, ein Objekt komplett zu klonen. Da dies aber mit mehreren unterschiedlichen Objekten passieren muß, dachte ich, es wäre wohl am sinnvollsten, per Reflection sich alle Felder zu holen und diese dann entsprechend zu setzen.

Nur habe ich das Gefühl, die Field.get()-Methode tut nicht das, was sie tun sollte.

Laut Javadoc gibt sie mir den Wert des Feldes zurück, ich bekomme aber immer nur den Namen. Ein kleines Codebeispiel:

Code:
Field[] fields = ((Auftraege)currentRow).getClass().getFields();
                    	
for(int i=0; i<fields.length; i++)
{
    try
    {	
        System.out.println("---------------   " + fields[i].get(currentRow));
        System.out.println("---------------   " + fields[i].get(currentRow).getClass());
        System.out.println("");
    }
//  catch...
}

Okay, nun werden also alle Felder des Objekts currentRow in einem Array gespeichert. Das geh ich durch und gib mir den Wert und danach den Typ aus. Eigentlich könnte man - laut Dokumentation - nun solch eine Ausgabe erwarten:

--------------- 17
--------------- class java.lang.Integer

--------------- 23.5
--------------- class java.lang.Float

--------------- hallo wie gehts
--------------- class java.lang.String

Tatsächlich gibt er aber nicht den Wert aus, sondern den Namen des Feldes. Und folglich in der jeweils zweiten Zeile, wo die Klasse des gewünschten Wertes angezeigt werden soll, immer java.lang.String. Aber das sollte doch eigentlich gar nicht der Fall sein?

Ist nun die Doku im Eimer oder verstehe ich was falsch?
 
Ich glaub bei dir ist irgendwas im Eimer 😉 ... - bei mir funktioniert folgendes wunderbar:

Code:
import java.lang.reflect.Field;

public class Test2 {

	public int x = 7;

	public String test = "testy!";


	public static void main  (String[] args) throws Exception {
		Test2 test2 = new Test2();


		Field[] fields = test2.getClass().getFields();

		for(int i=0; i<fields.length; i++)
		{
			System.out.println("---------------   " + fields[i].get(test2));
			System.out.println("---------------   " + fields[i].get(test2).getClass());
			System.out.println("");
		}
	}
}

check also mal dein originäres objekt... evtl. auch was da tatsächlich für werte drin sind...

einige Hinweise zum clonen: Achtung 😉 ... - da ich das gerade gemacht hab, weiß ich, dass das nicht soo trivial ist:
1.) mit getFields bekommst du nur die public Felder deiner Klasse - beim Kopieren sollten aber vermutlich auch die anderen Felder relevant sein - da hilft getDeclaredFields.
2.) Auch diese Methode liefert leider nur die Felder der aktuellen Klasse, sollten Felder existieren, die durch Vererbung mitgenommen wurden, können die so nicht gefunden werden. Da hilft nur ne eigene kleine Methode zu schreiben, die in der Vererbungshierarchie hoch geht und alle Felder der Klasse und aller Superklassen in ein entsprechendes Field[] Array sammelt.
3.) willst du deep-copy oder shallow-copy machen? Wenn manche Felder in deiner zu kopierenden Klasse nicht primitiv sind, werden bei einer shallow-copy lediglich die Referenzen kopiert, d.h. die Objekte (inkl. all dessen Felder die ebenfalls nicht primitiv sein könnten) werden nicht auch kopiert... falls nur primitive Felder auftreten können ist das ganze dann nicht soo schwierig.
4.) Du musst beim Instanziieren von den Objektkopien (die du ja vermutlich zurückgeben willst) gucken ob die Klasse nen parameterlosen Konstruktor besitzt... - wenn nicht, hast du ein Problem (die Lösung dazu hab ich hier aber auch schonmal gepostet - funktioniert mit der ReflectionFactory aus sun.reflect...)...
5.) Aufpassen bei deep-copy und Zyklen in der Objektstruktur... da musst du dir was geeignetes überlegen - z.B. alle zu kopierenden objekte und deren kopie in einer map speichern (achtung bei klassen die hashcode() überschreiben).

... und und und 😉 - da meine lösung Bestandteil eines Frameworks ist, welches ich nicht einfach posten darf, kann ich leider auch keinen Beispielcode posten, wie man das machen könnte, daher nur die paar Punkte oben...
evtl. solltest du dir dann sowas angucken: das ganze geht erheblich einfacher zu implementieren 😉 - mein ReflectionCopier ist immerhin inzwischen über 300 Zeilen groß.
 
SnooP hat gesagt.:
[doppelpost... - Server ist bei mir grad ziemlich hangy! 😉 - kann das sein?]

Tja, der ist wohl auch im Eimer 😀

Hmm okay, das scheint ja echt kein einfaches Unterfangen zu sein. Es wundert mich aber irgendwie, daß es da nicht standardmäßig schon was in Java gibt - ich glaub kaum, daß ich der einzige bin, der Objekte kopieren will 🙂

Ich hab jetzt ein Beispiel gefunden, wie man über einen ByteStream das Objekt kopieren kann, es muß dafür aber Serializable sein. Naja werde mir das wohl mal anschauen müssen.

Das Objekt selbst besteht eigentlich nur aus primitiven Typen (soweit ich weiß), denn das ist so eine Art Datensatz der dann komplett in 'ne DB geschrieben wird. Und da ich gerade eine Zumüll-Funktion basteln will, wo ich einen einzigen Datensatz (der zunächst mal als Java-Objekt vorliegt) z.b. 25000x kopieren können will, muß ich eben (in der momentanen Implementierung der kompletten Anwendung) eine 1:1-Kopie des Datensatzes haben und diesen dann nochmal in den Bestand einfügen.

Gibt es denn noch völlig andere Möglichkeiten, Objekte zu kopieren?
 
also es gibt imho vier möglichkeiten:
1. java objektserialisierung (ObjectOutputStream über ByteStream - siehe link von mir) - geht am schnellsten
2. cloneable implementieren und clone() aufrufen lassen - problem ist, dass die klassen eine vernünftige clone methode implementieren müssen.
3. reflections nutzen... - aber nich soo einfach
4. eine andere serialisierung z.B. mit xml. Siehe XStream: http://xstream.codehaus.org/

letztere hat den Vorteil wie die Reflection Geschichte, dass keine Voraussetzungen an die zu kopierenden klassen gestellt werden, sprich nix Serializable implementieren etc. - zudem kopiert der Kopierer als transient deklarierte Felder nicht mit - das geht bei den Reflections so ohne weiteres nicht, da man ansonsten zentrale Sachen wie ArrayLists oder sowas nicht kopieren könnte.

Von der Geschwindigkeit ist mein ReflectionKopierer inzwischen fast vier mal so schnell wie der Ansatz über XStream wobei die objektserialisierung etwas weniger als doppelt so schnell ist wie bei mir...

wenn man nur shallow-copies braucht, weil man nur primitive Felder hat (wobei man gucken muss, ob Strings als primitiv gezählt werden können...), dann geht die Geschichte sicher einfacher... - dann entfällt nämlich der rekursive Durchlauf und man kann halt einfach alle Felder 1:1 kopieren, das wird dann auch nicht mehr soo komplex.

Warum es da nix fertiges gibt? .. gute Frage 😉 ... - das einfache Kopieren bei java soll halt über den clone ansatz laufen. Wenn man eigene Datenstrukturen hat, geht das ja auch... aber manchmal kommt man halt in die Verlegenheit und muss über komplett fremde Klassen operieren und da gibts tatsächlich noch nix. Und so weit ich gesehen hab, wurde auch noch kein ReflectionCopier irgendwo veröffentlicht... - werde also demnachst mal nachhaken, ob ich das Ding aus dem Projekt auslösen kann und hier mal posten darf 😉
 
Mir ist jetzt eine andere Lösung eingefallen, auch wenn die auf den ersten Blick nicht optimal aussieht - aber ich hab mir jetzt die Klassen mal etwas genauer angeschaut und ich glaube, daß es damit genügen sollte.

Und zwar hat jede Eigenschaft, die in meinem Fall von Bedeutung ist, eine set- und get-Methode, und bei beiden hab ich eigentlich eine Garantie, daß sie immer exakt gleich heißen (bis auf das set und get natürlich 😀). Somit werd ich mir wohl einfach alle Methoden holen, diese parsen und aussortieren, und dann eben ein neues Objekt erstellen und alle set-Methoden aufrufen und als Wert den der entsprechenden get-Methode übergeben.

Wie gesagt, es klingt komisch, aber in diesem Fall dürfte das völlig ausreichen. Ich werd's mal versuchen und dann Bescheid sagen, ob das ging oder nicht.

---

Das mit dem Serializable kann ich leider vergessen, hab es vorhin versucht, aber gerade WEIL in den Klassen noch so viele Referenzen usw sind (die aber in meinem Fall unwichtig sind), müßte ja jedes dieser Objekte serializable sein, und ich glaub kaum, daß das in Ordnung geht, wenn ich jetzt alle möglichen Klassen einfach mal serializable mach 😉 denn eigentlich geht es nur drum, ein kleines Test-Tool in die Anwendung zu bauen, und dafür wollte ich möglichst wenig am vorhandenen Code rumbasteln.

Das XML-Teil werd ich mir dennoch mal anschauen, vielleicht nützt das in meinem Fall ja doch was. Wenn's allerdings zu langsam ist, ist das auch wieder nicht gut, denn das Kopieren sollte schon recht flott von der Hand gehen (weil ja massenweise Datensätze erstellt werden sollen).
 
Jo klingt doch gut... - ne javabean-Eigenschaft wollte ich vorhin schon nach fragen. Das kann man doch ausnutzen, wobei die Komplexität dadurch nicht besser oder schlechter wird 😉 ... ob du jetzt nun per Reflection die Felder ausliest und neu setzt oder aber per get/set Methoden (die du ja letztlich auch per reflection auslesen musst? oder gibt es nen Interface auf das du zurückgreifen kannst?) ... das einzige, was du dabei nicht machen musst, ist die private bzw. final Felder vorher mit der Methode setAccessible(true) verfügbar zu machen und das ist immer abhängig von jeweiligem SecurityManager ob das geht oder nicht (geht allerdings schon in den meisten Fällen).

Der xml-ansatz dauert bei mir bei 250 Objekten, die ich 1000 mal hintereinander kopiere rund 7 Sekunden. Mein Ansatz braucht dafür knapp 3 Sekunden und die Objektserialisierung 1,6 Sekunden... - ein deepclone mit clone-Methoden würde sich übrigens noch deutlich unter einer Sekunde befinden...
 
Okay, es funktioniert jetzt. Ich ermittle alle set- und get-Methoden, prüfe dann, ob es zu jeder set eine passende get gibt, wenn ja, wird die invoked mit dem entsprechenden Wert. Noch funktioniert es aber nicht bei allen Datensätzen, wobei es aber sein kann, daß das jetzt nicht speziell an meinem Zeugs liegt sondern an anderen Umständen, aber das werd ich noch rausfinden 🙂
 
Bist du in der Lage die Klassen der zu kopierenden Objekte selbst zu ändern, oder sind die extern?
Wenn du sie ändern kannst, währe ein Copy-Konstruktor vermutlich der richtige Ansatz.
 
Ja das könnte ich schon machen, aber es sind sehr viele und zudem sehr umfangreiche, und da aber diese Funktionalität nur für mich zum Testen benutzt wird, ist es fraglich, ob der Rest der Firma das so gern sieht, wenn da (in deren Augen dann) "unnötige" und ebenfalls sehr umfangreiche Methoden / Konstruktoren auftauchen... werde aber mal mit jemand sprechen, evtl. gibt's ja doch Bedarf an solchen Funktionen 😉
 
Und "clone" könnt ihr nicht implementieren? Das wäre auf jeden Fall weniger Aufwand als ein Copy-Constructor.

Code:
public class A implements Cloneable{
  int a, b, c, d;
  String e, f, g, h;
  ...

  public A clone(){
    try{
      return (A)super.clone();  // reine Magie im Hintergrund :-)
    }
    catch( CloneNotSupportedException ex ){
      ex.printStackTrace();
    }
  }
}
 
Naja, ganz so einfach ists mit clone() doch auch nicht. Denn Du musst die Felder ja - wenn es sich um Objektreferenzen handelt - dann per Hand selbst kopieren, indem Du explizit per Hand die clone() der Member aufrufst und die Kopien neu zuweist. Und bei komplexeren Strukturen, wenn die Referenzen z.B. Zyklen erzeugen, dann ist das auch nicht mehr ganz so trivial implementiert.
 
wobei es sich ja wohl nur um primitive Typen handelt? Und wenn es nur fürs Testen geht, dann sollte man ne Lösung nehmen, die möglichst nix mit den Originalklassen anstellt und nen copy-Konstruktor macht imho nur Sinn, wenn wenige Felder für eine Kopie tatsächlich relevant sind... beispielsweise bei String. Aber wenn ich mehr als 5 Felder habe zum Kopieren, dann wird son Konstruktor doch arg unhandlich 😉

also ich würde schon Reflection nehmen... oder aber evtl. mal xstream ausprobieren, weil das wenige zeilen code sind und man hat fix nen ergebnis, vor allem nen Referenzergebnis, mit dem man dann eigene Ansätze vergleichen kann.
 
Für clone() müßte ich doch die Methode komplett selbst schreiben, oder? Oder reicht da etwa das Codebeispiel von Dir (Beni) völlig aus?

Weil ich hab versucht, eine Klasse Cloneable zu machen, und dann hieß es halt, ich soll die clone()-Methode implementieren. Also muß ich dafür doch eigentlich jede einzelne Eigenschaft "übertragen" oder nicht?
 
Ja, jede Klasse muss cloneable implementieren und eine clone() Methode haben. super.clone() kopiert nur primitive Felder. Wenn die Klasse Referenzen besitzt, dann werden nur die Referenzen kopiert, nicht aber die Objekte, auf die diese zeigen. Das Code-Beispiel von Beni reicht also nur aus, wenn die Klasse entweder nur primitive Member hat oder Du nur eine flache statt einer tiefen Kopie haben willst.
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben