Wie kann man Klassenattribute in Java durchlaufen?

85

Wie kann ich Klassenattribute in Java dynamisch durchlaufen.

Zum Beispiel:

public class MyClass{
    private type1 att1;
    private type2 att2;
    ...

    public void function(){
        for(var in MyClass.Attributes){
            System.out.println(var.class);
        }
    }
}

ist das in Java möglich?

Zakaria
quelle

Antworten:

100

Es gibt keine sprachliche Unterstützung, um das zu tun, wonach Sie fragen.

Sie können zur Laufzeit reflektierend auf die Mitglieder eines Typs zugreifen, indem Sie Reflection verwenden (z. B. Class.getDeclaredFields()um ein Array von zu erhalten Field). Je nachdem, was Sie tun möchten, ist dies möglicherweise nicht die beste Lösung.

Siehe auch

Verwandte Fragen


Beispiel

Hier ist ein einfaches Beispiel, um nur einen Teil dessen zu zeigen, wozu Reflexion in der Lage ist.

import java.lang.reflect.*;

public class DumpFields {
    public static void main(String[] args) {
        inspect(String.class);
    }
    static <T> void inspect(Class<T> klazz) {
        Field[] fields = klazz.getDeclaredFields();
        System.out.printf("%d fields:%n", fields.length);
        for (Field field : fields) {
            System.out.printf("%s %s %s%n",
                Modifier.toString(field.getModifiers()),
                field.getType().getSimpleName(),
                field.getName()
            );
        }
    }
}

Das obige Snippet verwendet Reflexion, um alle deklarierten Felder von zu untersuchen class String. es erzeugt die folgende Ausgabe:

7 fields:
private final char[] value
private final int offset
private final int count
private int hash
private static final long serialVersionUID
private static final ObjectStreamField[] serialPersistentFields
public static final Comparator CASE_INSENSITIVE_ORDER

Effektive Java 2nd Edition, Punkt 53: Schnittstellen der Reflexion vorziehen

Dies sind Auszüge aus dem Buch:

Bei einem ClassObjekt können Sie erhalten Constructor, Methodund FieldInstanzen , die die Konstrukteure, Methoden und Felder der Klasse. Mit [Sie] können Sie die zugrunde liegenden Gegenstücke reflektierend manipulieren . Diese Kraft hat jedoch ihren Preis:

  • Sie verlieren alle Vorteile der Überprüfung zur Kompilierungszeit.
  • Der Code, der für den reflektierenden Zugriff erforderlich ist, ist ungeschickt und ausführlich.
  • Die Leistung leidet.

In der Regel sollte zur Laufzeit in normalen Anwendungen nicht reflektierend auf Objekte zugegriffen werden.

Es gibt einige anspruchsvolle Anwendungen, die Reflexion erfordern. Beispiele sind [... absichtlich weggelassen ...] Wenn Sie Zweifel haben, ob Ihre Bewerbung in eine dieser Kategorien fällt, ist dies wahrscheinlich nicht der Fall.

Polygenschmierstoffe
quelle
In der Tat möchte ich je nach Wert der Felder den Wert jedes Feldes schreiben oder nicht?
Zakaria
@Zakaria: Ja, Sie können dies mit Reflexion erreichen, aber je nachdem, was Sie versuchen, gibt es viel bessere Designs.
Polygenelubricants
Tatsächlich muss ich einen Bericht aus einer Klasse generieren. Abhängig vom Wert der Klassenfelder, die ich einfügen möchte oder nicht, welches Design kann ich am besten implementieren?
Zakaria
1
@Zakaria: Probieren Sie es dann aus. Hier Field.getkönnen Sie die Werte eines Feldes reflektierend lesen. Wenn ja private, können Sie das vielleicht umgehen setAccessible. Ja, Reflexion ist sehr leistungsfähig und ermöglicht es Ihnen private, finalFelder zu inspizieren, Felder zu überschreiben , privateKonstruktoren aufzurufen usw. Wenn Sie weitere Unterstützung beim Entwurfsaspekt benötigen, sollten Sie wahrscheinlich eine neue Frage mit viel mehr Informationen schreiben. Vielleicht brauchen Sie diese vielen Attribute überhaupt nicht (z. B. use enumoder Listusw.).
Polygenschmierstoffe
1
Generika haben hier keine Verwendung. einfach tunstatic void inspect(Class<?> klazz)
user102008
45

Der direkte Zugriff auf die Felder ist in Java kein wirklich guter Stil. Ich würde vorschlagen, Getter- und Setter-Methoden für die Felder Ihrer Bean zu erstellen und dann die Klassen Introspector und BeanInfo aus dem Paket java.beans zu verwenden.

MyBean bean = new MyBean();
BeanInfo beanInfo = Introspector.getBeanInfo(MyBean.class);
for (PropertyDescriptor propertyDesc : beanInfo.getPropertyDescriptors()) {
    String propertyName = propertyDesc.getName();
    Object value = propertyDesc.getReadMethod().invoke(bean);
}
Jörn Horstmann
quelle
Gibt es eine Möglichkeit, nur Eigenschaften der Bohne zu erhalten, nicht der Klasse? dh vermeiden Sie MyBean.class von getPropertyDescriptors zurückgegeben
hbprotoss
@hbprotoss Wenn ich Sie richtig verstehe, können Sie nach suchen propertyDesc.getReadMethod().getDeclaringClass() != Object.classoder eine Klasse angeben, um die Analyse als zweiten Parameter zu stoppen getBeanInfo(MyBean.class, Object.class).
Jörn Horstmann
20

Ich stimme Jörns Antwort zu, wenn Ihre Klasse der JavaBeabs-Spezifikation entspricht, aber hier ist eine gute Alternative, wenn dies nicht der Fall ist und Sie Spring verwenden.

Spring verfügt über eine Klasse namens ReflectionUtils , die einige sehr leistungsstarke Funktionen bietet, darunter doWithFields (Klasse, Rückruf) , eine Methode im Besucherstil, mit der Sie mithilfe eines Rückrufobjekts wie folgt über Klassenfelder iterieren können:

public void analyze(Object obj){
    ReflectionUtils.doWithFields(obj.getClass(), field -> {

        System.out.println("Field name: " + field.getName());
        field.setAccessible(true);
        System.out.println("Field value: "+ field.get(obj));

    });
}

Aber hier ist eine Warnung: Die Klasse ist als "nur für den internen Gebrauch" gekennzeichnet, was schade ist, wenn Sie mich fragen

Sean Patrick Floyd
quelle
13

Einfache Möglichkeit, Klassenfelder zu durchlaufen und Werte vom Objekt abzurufen:

 Class<?> c = obj.getClass();
 Field[] fields = c.getDeclaredFields();
 Map<String, Object> temp = new HashMap<String, Object>();

 for( Field field : fields ){
      try {
           temp.put(field.getName().toString(), field.get(obj));
      } catch (IllegalArgumentException e1) {
      } catch (IllegalAccessException e1) {
      }
 }
Rickey
quelle
Eine Klasse vielleicht oder irgendein Objekt.
Rickey
@Rickey Gibt es überhaupt eine Möglichkeit, den Attributen durch Schleifen der einzelnen Felder einen neuen Wert zuzuweisen?
hyperfkcb
@hyperfkcb Möglicherweise wird eine Änderungsausnahme angezeigt, wenn Sie versuchen, Werte von Eigenschaften / Feldern zu ändern, über die Sie iterieren.
Rickey
6

Java hat Reflection (java.reflection. *), Aber ich würde vorschlagen, in eine Bibliothek wie Apache Beanutils zu schauen, da dies den Prozess viel weniger haarig macht als die direkte Verwendung von Reflection.

Tassos Bassoukos
quelle
0

Hier ist eine Lösung, die die Eigenschaften alphabetisch sortiert und sie alle zusammen mit ihren Werten druckt:

public void logProperties() throws IllegalArgumentException, IllegalAccessException {
  Class<?> aClass = this.getClass();
  Field[] declaredFields = aClass.getDeclaredFields();
  Map<String, String> logEntries = new HashMap<>();

  for (Field field : declaredFields) {
    field.setAccessible(true);

    Object[] arguments = new Object[]{
      field.getName(),
      field.getType().getSimpleName(),
      String.valueOf(field.get(this))
    };

    String template = "- Property: {0} (Type: {1}, Value: {2})";
    String logMessage = System.getProperty("line.separator")
            + MessageFormat.format(template, arguments);

    logEntries.put(field.getName(), logMessage);
  }

  SortedSet<String> sortedLog = new TreeSet<>(logEntries.keySet());

  StringBuilder sb = new StringBuilder("Class properties:");

  Iterator<String> it = sortedLog.iterator();
  while (it.hasNext()) {
    String key = it.next();
    sb.append(logEntries.get(key));
  }

  System.out.println(sb.toString());
}
Benny Neugebauer
quelle