Reflection ändern von Objekten in Objekten

BodyLAB

Bekanntes Mitglied
Hallo zusammen,

ich habe den Reflection-Mechanismus entdeckt und stelle mir dazu einige Frage.
Ist es möglich Objekte zu verändern die z. B. in einer Liste gehalten werden?

Hier einmal ein kleines Programm was ich derzeit geschrieben habe:
Java:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.*;
import java.util.*;

public class TestReflection {
    
    public static void main(String[] args) {
        try {                    
            Class c = Class.forName("Aufgabe1.Blob");
            Object o = c.getConstructor(String.class, String.class).newInstance("Blob", "blob");
            System.out.println(o);
            
            Constructor constructor = Blob.class.getConstructor(String.class, String.class); 
            Blob d = (Blob) constructor.newInstance("Test", "test");
            System.out.println(d);
            
            Method m1 = c.getDeclaredMethod("getVorname");
            m1.setAccessible(true);
            System.out.println(m1.invoke(o));

            Method m2 = c.getDeclaredMethod("setVorname", String.class);
            m2.setAccessible(true);
            m2.invoke(o, "Neuer Name");
            System.out.println(o);

            System.out.println("Class:\n" + c + "\nMethods:");
            List<Method> methods = Arrays.asList(c.getDeclaredMethods());

            for(Method m : methods) {
                System.out.println(m);
            }

            System.out.println("Fields:");
            List<Field> fields = Arrays.asList(c.getDeclaredFields());

            for(Field f : fields) {
                System.out.println(f);
            }

           ArrayList<Blob> meineBlobs = new ArrayList<>();
            
            for(int i = 0; i < 10; i++)
                meineBlobs.add(new Blob("Vorname " + i, "Nachname " + i));

        }catch(Exception e) {
            System.out.println(e.getMessage());
        }       
    }

}

Die Klasse Blob:
Java:
public class Blob {
    private String vorname, nachname;
    
    public Blob(String v, String n) {
        vorname = v;
        nachname = n;
    }

    public String getVorname() {
        return vorname;
    }
    
    public String getNachname() {
        return nachname;
    }
    
    public void setVorname(String v) {
        vorname = v;
    }
    
    public void setNachname(String n) {
        nachname = n;
    }
    
    public String toString() {
        return vorname + " " + nachname;
    }
    
}

Unten im Code hab ich mir die Frage gestellt ob ich auch Objekte direkt Manipulieren kann oder eben auch wenn sich diese Objekte in einer Datenstruktur befinden etc. Denn bis jetzt hab ich von meiner Klasse immer eine neue Instanz erstellt. Das führt dann eben zu einem neuen Objekt an dem ich alles verändern kann selbst Private Methoden oder Datenelemente aufrufen und verändern 🙂

Nun aber eben die Frage ist es auch möglich das ich die 10 Blubs in meiner ArrayListe irgendwie geändert bekomme?
 
field hat die get methode , damit kriegst du das was aktuell in dem field bei einem objekt der klasse drin steht

Java:
public double get(Object obj)
             throws IllegalArgumentException,
                    IllegalAccessException
                    
                    https://www.geeksforgeeks.org/field-get-method-in-java-with-examples/

in deinem wäre es dann
Code:
Field namensFeld = ...

for( i ...in liste )
    var namedesblobs = namensFeld.get(list.get(i))
    ...
 
Du kannst per Reflection auf alles zugreifen und dann natürlich auch verändern. Wenn Du also eine ArrayList hast, dann kannst Du da durch die Liste gehen um dann alle Elemente zu verändern.

Dabei kannst Du natürlich hin gehen, und die ArrayList per Reflection durchgehen. Du kannst aber auch prüfen, ob die Instanz (die eine ArrayList ist) das Interface List implementiert um dann direkt die List zu nutzen. Bei sowas ist also keine Reflection notwendig.

Generell sollte man sich aber immer überlegen, ob man wirklich Reflection nutzen muss oder ob man nicht auf andere Möglichkeiten zugreifen kann. Reflection hat halt ein schlechtes Laufzeitverhalten und man hat die ganzen Typ-Prüfungen nicht mehr.
 
in deinem wäre es dann
Okay das verstehe ich nicht. Ich benötige ja sämtliche Elemente aus meineBlobs, doch wie erstelle ich ein Field dafür? Da komme ich nicht mit.

Hab dieses Beispiel gefunden:
Java:
import java.lang.reflect.Field;
 
public class GFG {
 
    public static void main(String[] args)
        throws Exception
    {
 
        // Create the User class object
        User user = new User();
 
        // Get the all field objects of User class
        Field[] fields = User.class.getFields();
 
        for (int i = 0; i < fields.length; i++) {
 
            // get value of the fields
            Object value = fields[i].get(user);
 
            // print result
            System.out.println("Value of Field "
                               + fields[i].getName()
                               + " is " + value);
        }
    }
}
 
// sample User class
class User {
 
    // static double values
    public static double Marks = 34.13;
    public static int Fees = 34199;
    public static String name = "Aman";
 
    public static double getMarks()
    {
        return Marks;
    }
 
    public static void setMarks(double marks)
    {
        Marks = marks;
    }
 
    public static int getFees()
    {
        return Fees;
    }
 
    public static void setFees(int fees)
    {
        Fees = fees;
    }
 
    public static String getName()
    {
        return name;
    }
 
    public static void setName(String name)
    {
        User.name = name;
    }
}

Nur verstehe ich nicht wie ich an die Daten der Liste kommen soll.
Kann mir dabei jemand weiterhelfen?
 
Zuletzt bearbeitet:
Nö ich steig nicht dahinter!
Java:
Field[] fields = ArrayList.class.getFields();  
            for(int i = 0; i < fields.length; i++)
                System.out.println(fields[i]);

Ansonsten finde ich nichts wie man mithilfe von Reflection über eine ArrayListe iterieren kann. Kann es sein das so etwas überhaupt nicht möglich ist.
Werde morgen mal mit einer normalen Schleife über die Liste laufen und dann die Werte mit Reflection ändern.
Vielleicht hat jemand von euch eine Idee (falls es überhaupt geht) wie man über eine Liste iteriert mithilfe von Reflection ;-)
 
Zuletzt bearbeitet:
Wenn Du mit Reflection arbeiten willst, dann schau bitte genau in die Dokumentation. Das ist ein Bereich, in dem man exakt arbeiten muss und da sollte man wirklich die Dokumentation der Methoden lesen, ehe man diese benutzt.


Bei getFields findet sich z.B. als kurze Dokumentation:
Returns an array containing Field objects reflecting all the accessible public fields of the class or interface represented by this Class object

Vermutlich sind die Fields einfach nicht public. Aber Du hast doch die Methoden schon gefunden. Du könntest also die Setter nutzen, um da Werte zu setzen.

Ansonsten kannst Du die Fields mit getDeclaredFields bekommen. Da wäre die Dokumentation:
Returns an array of Field objects reflecting all the fields declared by the class or interface represented by this Class object.
 
Java:
Field[] fields = ArrayList.class.getFields();
            for(int i = 0; i < fields.length; i++)
                System.out.println(fields[i]);
der denkfehler ist hier dass du dir die fields von der arraylist holst...

ein "Field" gehört zu einer klasse und nicht zu einem objekt

ein field ist dafür da um aus einem objekt dieser klasse "den wert raus zu holen" ......... simpel ausgedrückt
 
ein "Field" gehört zu einer klasse und nicht zu einem objekt
Ja, das hat mich hier an der ganzen Sache so verwirrt. Ich dachte die ganze Zeit "was wollt Ihr das kann nicht gehen" 🙃

Werde mir wie @KonradN schon gesagt hat erst einmal die Doku genauer anschauen. Hab hier zu schnell für wirbel gesorgt Entschuldigung.

Doch was ich bis jetzt raus bekommen habe ist das hier:
Java:
ArrayList<Blob> meineBlobs = new ArrayList<>();
            Blob miniBlob = new Blob("Mini", "Blob");
           
            for (int i = 0; i < 10; i++)
                meineBlobs.add(new Blob("Vorname " + i, "Nachname " + i));
           
Field[] fields = miniBlob.getClass().getDeclaredFields();
            for(Field field : fields) {
                System.out.println(field);
            }

Mit der ArrayList ist mir das am Anfang zu kompliziert.
Ich werde nun als erstes einmal versuchen das Objekt miniBlob über Reflection zu manipulieren. Das ganze ist ja sehr sehr Interessant. Wenn ich dann von miniBlob den Vorname Mini z. B. in MINI geändert bekomme schaue ich wie es mit der ArrayList funktioniert. Erst einmal kleine Brötchen backen

😉



Wahnsinn das geht ja wirklich:
Java:
            Blob miniBlob = new Blob("Mini", "Blob");
            Field[] fields = miniBlob.getClass().getDeclaredFields();
            String input = "MINI";
            Field mini = fields[0];
            mini.setAccessible(true);
            mini.set(miniBlob, input);
            System.out.println(miniBlob);
 
Zuletzt bearbeitet:
Schon verzweifel ich wieder!
Meine Idee war jetzt erst einmal, ganz nach dem Prinzip Teile und Herrsche, die Herausforderung in kleine Unterprobleme zu Unterteilen.
  1. Ein Objekt ist gegeben und dort werden Felder geändert (Check siehe oben)
  2. Sich mit Reflection die Size der ArrayListe nehmen und so lange eine Schleife bauen wie size groß ist
  3. Sich mit Reflection eine set Methode bauen um die ArrayList-Element über die set Methode zu Manipulieren
  4. Sich mit Reflection eine get Methode bauen um die ArrayList-Elemente über diese Methode zu erhalten
  5. Danach sollte doch alles über Reflection abgewickelt sein bzw. werden
Doch leider funktioniert schon Punkt 3 nicht so richtig. Vermutlich geht das ganze auch viel besser, schneller und einfacher. Nur fällt mir gerade kein anderer Weg ein. Bin über jede Hilfe und jeden Beitrag von euch sehr froh 🙂
Hier mal mein Code bis jetzt:
Java:
ArrayList<Blob> arrayBlobs = new ArrayList<>();
            for (int i = 0; i < 10; i++)
                arrayBlobs.add(new Blob("Vorname " + i, "Nachname " + i));
          
            Field[] myFields = arrayBlobs.getClass().getDeclaredFields();
            String hacked = "Hacked"; //soll spaeter bei jedem Blob als Vorname stehen =)
          
//            Wie bekommt man size ueber Field?
//            Field f = myFields[5];
//            f.setAccessible(true);
//            int size = (int) f.getInt("size");
//            System.out.println(size);
          
//            Da ich nicht verstehe wie ich an size mit Field dran komme eben so:
             Method sizeMethod = arrayBlobs.getClass().getDeclaredMethod("size");
             sizeMethod.setAccessible(true);
             int size = (int) sizeMethod.invoke(arrayBlobs);

             Method setMethod = arrayBlobs.getClass().getDeclaredMethod("set");
             setMethod.setAccessible(true);
          
             for(int i = 0; i < size; i++) {
                 setMethod.invoke(arrayBlobs.get(i), hacked);
             }
          
             System.out.println(arrayBlobs);

Mit dem Pseudocode
Code:
Field namensFeld = ...

for( i ...in liste )
    var namedesblobs = namensFeld.get(list.get(i))
    ...
Kann ich leider auch nichts Anfangen :-(
Java:
            Field[] myFields = arrayBlobs.getClass().getDeclaredFields();
            String hacked = "Hacked"; //soll spaeter bei jedem Blob als Vorname stehen =)
            Field f = myFields[4];
            System.out.println(f);
           
            for(Blob i : arrayBlobs)
                System.out.println(f.get(i));
Ich weiß nicht mal ob das richtig wäre Field f in myFields[4] zu schreiben, werden die Elemente eine ArrayListe im Datenelement elementData gespeichert?

____________________
Also so würde es natürlich funktionieren (nur wollte ich das nicht so):
Java:
            Field[] fields = miniBlob.getClass().getDeclaredFields();
            String input = "MINI";
            Field mini = fields[0];
            mini.setAccessible(true);

            ArrayList<Blob> arrayBlobs = new ArrayList<>();
            for (int i = 0; i < 10; i++)
                arrayBlobs.add(new Blob("Vorname " + i, "Nachname " + i));
            
            String hacked = "Hacked";
            
            for(Blob b : arrayBlobs) {
                mini.set(b, hacked);
            }
            System.out.println(arrayBlobs);
 
Zuletzt bearbeitet:
Ja, die Elemente einer ArrayListe werden in elementData gespeichert. Daher wäre ein Zugriff in der Art und Weise denkbar:
Java:
package de.kneitzel;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;

public class ReflectionTests {
    public static class TestElement {
        private String name;
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public TestElement(String name) { this.name = name; }
    }

    public static void main(String[] args) throws Exception {
        // Prepare Test Data
        ArrayList<TestElement> list = new ArrayList<>();
        list.add(new TestElement("Anton"));
        list.add(new TestElement("Berta"));

        // Access through reflection
        Field[] fields = list.getClass().getDeclaredFields();
        Field elementDataField = Arrays.stream(fields)
                .filter( f -> Objects.equals("elementData",f.getName()) )
                .findAny().orElseThrow(() -> new IllegalStateException("elementaData not found!"));

        // elementData is private - lets change it to public
        // Ab Java 9 braucht man ein: --add-opens java.base/java.util=ALL-UNNAMED
        elementDataField.setAccessible(true);

        // Object Array holen
        Object[] elementData = (Object[]) elementDataField.get(list);

        // Jetzt gibt es keinen Grund, hier nicht auf die Elemente des Arrays zuzugreifen.
        // also elementData[0] und so
        // Alternative: Arrays.get(elementData, 0)
        
    }
}
 
Ja, die Elemente einer ArrayListe werden in elementData gespeichert. Daher wäre ein Zugriff in der Art und Weise denkbar:
Wow genau so hab ich es mir vorgestellt.
Hab das ganze mit meinem noch Kombiniert.
Java:
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;

public class ReflectionTest {

    public static class TestElement {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public TestElement(String name) {
            this.name = name;
        }
        
        public String toString() {
            return name;
        }
    }

    public static void main(String[] args) throws Exception {
        // Prepare Test Data
        ArrayList<TestElement> list = new ArrayList<>();
        list.add(new TestElement("Anton"));
        list.add(new TestElement("Berta"));

        // Access through reflection
        Field[] fields = list.getClass().getDeclaredFields();
        Field elementDataField = Arrays.stream(fields).filter(f -> Objects.equals("elementData", f.getName())).findAny()
                .orElseThrow(() -> new IllegalStateException("elementaData not found!"));

        // elementData is private - lets change it to public
        // Ab Java 9 braucht man ein: --add-opens java.base/java.util=ALL-UNNAMED
        elementDataField.setAccessible(true);

        // Object Array holen
        Object[] elementData = (Object[]) elementDataField.get(list);

        // Jetzt gibt es keinen Grund, hier nicht auf die Elemente des Arrays
        // zuzugreifen.
        // also elementData[0] und so
        // Alternative: Arrays.get(elementData, 0)
                Field[] fieldsTestElement = TestElement.class.getDeclaredFields();
        String input = "MINI";
        Field name = fieldsTestElement[0];
        name.setAccessible(true);
        
        for(Object o : elementData)
            if(o != null)
            name.set(o, input);
        
        for(Object o : elementData)
            System.out.println(o);
    }
}

So jetzt habe ich aber noch eine Frage wieso ist elementData größer als 2? Liegt das daran das die ArrayList immer mehr Element anlegt um Performanz zu sein?
Auf ein Array.stream wäre ich auch nicht gekommen (damit habe ich leider zu lange nicht mehr gearbeitet ärgerlich) doch wenn ich diese Zeile richtig verstehe:
Java:
Arrays.stream(fields).filter(f -> Objects.equals("elementData", f.getName())
Erstellst du eben ein Stream und läufst über die einzelnen Felder. Sobald f den Namen (f.getName gibt den Namen des Elements zurück also bei private int size eben size) "elementData" hat, hast du das Feld gefunden um das es geht. Coole Idee 🙂

Jetzt bin ich zufrieden und hab wieder viel gelernt ;-)
Nun hätte ich aber noch eine weitere Frage und zwar was kann man denn genau mit Reflection machen? Ich weiß das es ein SecurityManager gibt der eben gewisse Sachen verhindert (sonst wäre Java ja nicht sicher und jeder macht was er will mit so einem mächten Werkzeug). Nur wozu benutzt man Reflection? Hab es jetzt beim rum suchen viel bei @Tests gesehen
 
wieso ist elementData größer als 2?
Die Liste soll möglichst performant sein. Daher wird immer in Blöcken gearbeitet und intern gespeichert, wie viel belegt ist. Und immer wenn ein weiteres Element hinzu gefügt wird und das Array bereits voll ist, wird ein neues Array erzeugt und alle Element des alten Arrays rüber kopiert. Das kostet natürlich Zeit und daher wird immer in Blöcken belegt. (Wenn ich mich recht erinnere, dann ist da sogar +50% mehr oder so vorgesehen.)

Erstellst du eben ein Stream und läufst über die einzelnen Felder. Sobald f den Namen (f.getName gibt den Namen des Elements zurück also bei private int size eben size) "elementData" hat, hast du das Feld gefunden um das es geht. Coole Idee 🙂
Nicht ganz. Der Code, den du dort gezeigt hast, ist nur:
a) Auf Basis des Arrays wird ein Stream erstellt. Alle Elemente des Arrays werden somit im Stream nacheinander bereit gestellt. (da passiert noch nichts!)
b) Dann wird ein Filter eingestellt: Jedes Element wird somit geprüft. Hier wird darauf geprüft, ob der Name gleich dem gesuchten Namen entspricht

Aber da passiert immer noch nichts. Das ist nur etwas, das bereit gestellt wird. Der wichtige Part kommt dann erst:
.findAny()
Hier wird nun endlich die Verarbeitung angestoßen. Bei diesem Anruf fängt nun die Verarbeitung an:
  • Die Elemente werden einzeln durchgegangen.
  • Ist die Bedingung des Filters erfüllt? Nein? -> Element wird nicht weiter behandelt. Ja? -> Wir haben einen Treffer, findAny bricht an der Stelle ab und gibt ein Optional.of vom Treffer zurück.
  • Wird generell nichts gefunden, wird ein Optional.empty zurück gegeben.

Auf diesem Optional kommt dann mit dem orElseThrow die Auswertung: Wir bekommen den Treffer zurück oder es wird eine Exception geworfen (über den bereitgestellten Lambda Ausdruck).
 
Nochmals vielen Dank.
Ja die Streams oder auch Lambdas müsste ich mir dringend auch erneut anschauen. Konnte ich alles auch mal besser.
Doch Ihr erklärt es jedes mal sehr ausführlich und gut verständlich 😎
 

Zurück
Oben