Domanda Java toString () utilizzando la riflessione?


Stavo scrivendo un toString () per una classe in Java l'altro giorno scrivendo manualmente ogni elemento della classe su una stringa e mi è venuto in mente che usando il reflection sarebbe stato possibile creare un metodo toString () generico che potesse funzionare su TUTTE le classi. OSSIA sarebbe capire i nomi dei campi e i valori e inviarli a una stringa.

Ottenere i nomi dei campi è abbastanza semplice, ecco cosa ha scoperto un collaboratore:

public static List initFieldArray(String className) throws ClassNotFoundException {

    Class c = Class.forName(className);
    Field field[] = c.getFields();
    List<String> classFields = new ArrayList(field.length);

    for (int i = 0; i < field.length; i++) {
        String cf = field[i].toString();
        classFields.add(cf.substring(cf.lastIndexOf(".") + 1));
    }

    return classFields;
}

Usando una factory potrei ridurre il sovraccarico delle prestazioni memorizzando i campi una volta, la prima volta che viene chiamato toString (). Tuttavia, trovare i valori potrebbe essere molto più costoso.

A causa delle prestazioni di riflessione questo può essere più ipotetico che pratico. Ma mi interessa l'idea della riflessione e come posso usarla per migliorare la mia programmazione quotidiana.


44
2018-01-14 16:45


origine


risposte:


Apache commons-lang ReflectionToStringBuilder fa questo per te.

import org.apache.commons.lang3.builder.ReflectionToStringBuilder

// your code goes here

public String toString() {
   return ReflectionToStringBuilder.toString(this);
}

78
2018-01-14 16:53



W / reflection, dato che non ero a conoscenza della libreria di apache:

(tieni presente che se lo fai dovrai probabilmente occuparti dei sottooggetti e assicurati che vengano stampati correttamente, in particolare gli array non ti mostreranno nulla di utile)

@Override
public String toString()
{
    StringBuilder b = new StringBuilder("[");
    for (Field f : getClass().getFields())
    {
        if (!isStaticField(f))
        {
            try
            {
                b.append(f.getName() + "=" + f.get(this) + " ");
            } catch (IllegalAccessException e)
            {
                // pass, don't print
            }
        }
    }
    b.append(']');
    return b.toString();
}


private boolean isStaticField(Field f)
{
    return Modifier.isStatic(f.getModifiers());
}

6
2018-01-14 18:23



Un'altra opzione, se stai bene con JSON, è la libreria GSON di Google.

public String toString() {
    return new GsonBuilder().setPrettyPrinting().create().toJson(this);
}

Sta per fare il riflesso per te. Questo produce un file JSON bello, facile da leggere. Essendo di facile lettura, le persone non tecnologiche potrebbero trovare il JSON intimidatorio.

Puoi anche fare di GSONBuilder una variabile membro, se non vuoi rinnovarla ogni volta.

Se si dispone di dati che non possono essere stampati (come uno stream) o dati che non si desidera stampare, è sufficiente aggiungere i tag @Expose agli attributi che si desidera stampare e quindi utilizzare la riga seguente.

 new GsonBuilder()
.setPrettyPrinting()
.excludeFieldsWithoutExposeAnnotation()
.create()
.toJson(this);

6
2018-01-19 17:28



Se stai usando Eclipse, puoi anche dare un'occhiata a Generatore di JUtils toString, che lo fa staticamente (generando il metodo nel codice sorgente).


5
2018-01-14 17:00



Non riflessione, ma ho dato un'occhiata al generare il metodo toString (insieme a equals / hashCode) come un passo post-compilazione usando la manipolazione bytecode. I risultati sono stati contrastanti.


4
2018-01-14 18:17



Puoi usare le librerie già implementate, come ReflectionToStringBuilder da Apache commons-lang. Come è stato menzionato.

Oppure scrivi da solo con te stesso API di riflessione.

Qui è qualche esempio:

class UniversalAnalyzer {

   private ArrayList<Object> visited = new ArrayList<Object>();

   /**
    * Converts an object to a string representation that lists all fields.
    * @param obj an object
    * @return a string with the object's class name and all field names and
    * values
    */
   public String toString(Object obj) {
      if (obj == null) return "null";
      if (visited.contains(obj)) return "...";
      visited.add(obj);
      Class cl = obj.getClass();
      if (cl == String.class) return (String) obj;
      if (cl.isArray()) {
         String r = cl.getComponentType() + "[]{";
         for (int i = 0; i < Array.getLength(obj); i++) {
            if (i > 0) r += ",";
            Object val = Array.get(obj, i);
            if (cl.getComponentType().isPrimitive()) r += val;
            else r += toString(val);
         }
         return r + "}";
      }

      String r = cl.getName();
      // inspect the fields of this class and all superclasses
      do {
         r += "[";
         Field[] fields = cl.getDeclaredFields();
         AccessibleObject.setAccessible(fields, true);
         // get the names and values of all fields
         for (Field f : fields) {
            if (!Modifier.isStatic(f.getModifiers())) {
               if (!r.endsWith("[")) r += ",";
               r += f.getName() + "=";
               try {
                  Class t = f.getType();
                  Object val = f.get(obj);
                  if (t.isPrimitive()) r += val;
                  else r += toString(val);
               } catch (Exception e) {
                  e.printStackTrace();
               }
            }
         }
         r += "]";
         cl = cl.getSuperclass();
      } while (cl != null);

      return r;
   }    
}

3
2017-09-20 22:16



Ecco i Netbeans equivalenti alla risposta di Olivier; plug-in smart-codegen per Netbeans.


2
2018-03-17 21:59