Jak zapętlić atrybuty klasy w Javie?

85

Jak mogę dynamicznie zapętlić atrybuty klasy w Javie.

Na przykład:

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

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

czy to możliwe w Javie?

Zakaria
źródło

Odpowiedzi:

100

Nie ma wsparcia językowego, aby zrobić to, o co prosisz.

Możesz w sposób refleksyjny uzyskiwać dostęp do elementów członkowskich typu w czasie wykonywania za pomocą refleksji (np. Za pomocą, Class.getDeclaredFields()aby uzyskać tablicę Field), ale w zależności od tego, co próbujesz zrobić, może to nie być najlepsze rozwiązanie.

Zobacz też

Powiązane pytania


Przykład

Oto prosty przykład, który pokazuje tylko część tego, do czego zdolna jest refleksja.

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()
            );
        }
    }
}

Powyższy fragment wykorzystuje odbicie, aby sprawdzić wszystkie zadeklarowane pola class String; generuje następujący wynik:

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

Efektywna Java 2. wydanie, pozycja 53: Preferuj interfejsy do refleksji

Oto fragmenty książki:

Mając Classobiekt, możesz uzyskać Constructor, Methodi Fieldinstancje reprezentujące konstruktory, metody i pola tej klasy. [Umożliwiają] refleksyjną manipulację ich podstawowymi odpowiednikami . Ta moc ma jednak swoją cenę:

  • Utracisz wszystkie korzyści płynące ze sprawdzania w czasie kompilacji.
  • Kod wymagany do wykonania refleksyjnego dostępu jest niezgrabny i rozwlekły.
  • Cierpi na tym wydajność.

Z reguły dostęp do obiektów nie powinien być odzwierciedlany w normalnych aplikacjach w czasie wykonywania.

Istnieje kilka wyrafinowanych aplikacji, które wymagają refleksji. Przykłady obejmują [... celowo pominięte ...] Jeśli masz jakiekolwiek wątpliwości, czy Twoje zgłoszenie należy do jednej z tych kategorii, prawdopodobnie tak nie jest.

smary wielogenowe
źródło
faktycznie, w zależności od wartości pól, chcę pisać czy nie wpisywać wartości każdego pola?
Zakaria
@Zakaria: tak, możesz to osiągnąć dzięki refleksji, ale w zależności od tego, co próbujesz zrobić, są znacznie lepsze projekty.
polygenelubricants
W rzeczywistości mam raport do wygenerowania z klasy i w zależności od wartości pól klasy, które chcę umieścić lub nie, jaki jest najlepszy projekt, który mogę zaimplementować.
Zakaria
1
@Zakaria: idź dalej i spróbuj zastanowić się. Jest to Field.get, czego możesz użyć do refleksyjnego odczytywania wartości pola. Jeśli tak private, możesz to obejść setAccessible. Tak, odbicie jest bardzo potężne i pozwala na takie rzeczy, jak sprawdzanie privatepól, nadpisywanie finalpól, wywoływanie privatekonstruktorów itp. Jeśli potrzebujesz dalszej pomocy przy projektowaniu, prawdopodobnie powinieneś napisać nowe pytanie z dużo większą ilością informacji. Być może w pierwszej kolejności nie potrzebujesz tych wielu atrybutów (np. Użyj enumlub Listitp.).
polygenelubricants
1
leki generyczne nie mają tutaj zastosowania. po prostu zróbstatic void inspect(Class<?> klazz)
user102008
45

Bezpośredni dostęp do pól nie jest dobrym stylem w Javie. Sugerowałbym utworzenie metod pobierających i ustawiających dla pól twojego beana, a następnie użycie klas Introspector i BeanInfo z pakietu java.beans.

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
źródło
Czy istnieje sposób, aby uzyskać tylko właściwości fasoli, a nie jej klasę? tj. unikaj MyBean.class zwracanego przez getPropertyDescriptors
hbprotoss
@hbprotoss, jeśli dobrze Cię rozumiem, możesz sprawdzić propertyDesc.getReadMethod().getDeclaringClass() != Object.classlub określić klasę, aby zatrzymać analizę jako drugi parametr, np getBeanInfo(MyBean.class, Object.class).
Jörn Horstmann
20

Chociaż zgadzam się z odpowiedzią Jörna, jeśli twoja klasa jest zgodna ze specyfikacją JavaBeabs, tutaj jest dobra alternatywa, jeśli tak nie jest i używasz Spring.

Spring ma klasę o nazwie ReflectionUtils, która oferuje bardzo potężne funkcje, w tym doWithFields (klasa, wywołanie zwrotne) , metodę w stylu gościa, która umożliwia iterację po polach klas przy użyciu obiektu wywołania zwrotnego, takiego jak ten:

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));

    });
}

Ale oto ostrzeżenie: klasa jest oznaczona jako „tylko do użytku wewnętrznego”, a szkoda, jeśli mnie zapytasz

Sean Patrick Floyd
źródło
13

Prosty sposób na iterację po polach klas i uzyskanie wartości z obiektu:

 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
źródło
Być może klasa lub jakikolwiek przedmiot.
Rickey
@Rickey Czy mimo to można ustawić nową wartość atrybutów, zapętlając poszczególne pola?
hyperfkcb
@hyperfkcb Podczas próby zmiany wartości właściwości / pól, nad którymi iterujesz, możesz otrzymać wyjątek dotyczący modyfikacji.
Rickey
6

Java ma Reflection (java.reflection. *), Ale sugerowałbym zajrzenie do biblioteki takiej jak Apache Beanutils, dzięki czemu proces będzie znacznie mniej skomplikowany niż bezpośrednie użycie odbicia.

Tassos Bassoukos
źródło
0

Oto rozwiązanie, które sortuje właściwości alfabetycznie i drukuje je wszystkie wraz z ich wartościami:

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
źródło