31 Stimmen

Java toString - ToStringBuilder nicht ausreichend; wird nicht durchlaufen

Ich muss in der Lage sein, meinen gesamten Objektgraphen zu durchlaufen und alle Inhalte aller Mitgliedsfelder zu protokollieren.

Zum Beispiel: Objekt A hat eine Sammlung von Objekten B, die eine Sammlung von Objekten C haben, und A, B, C haben zusätzliche Felder usw.

Apache Commons ToStringBuilder ist nicht ausreichend, da es weder einen Objektgraphen durchläuft noch den Inhalt einer Sammlung ausgibt.

Kennt jemand eine andere Bibliothek, die dies tun wird oder haben einen Code-Schnipsel, die dies tut?

59voto

dma_k Punkte 9996

Sie können den gesamten Baum durchlaufen, indem Sie org.apache.commons.lang.builder.ReflectionToStringBuilder . Der Trick ist, dass in ToStringStyle müssen Sie in den Wert übergehen. ToStringStyle kümmert sich um die bereits verarbeiteten Werte und lässt keine Rekursion zu. Los geht's:

System.out.println(ReflectionToStringBuilder.toString(schema, new RecursiveToStringStyle(5)));

private static class RecursiveToStringStyle extends ToStringStyle {

    private static final int    INFINITE_DEPTH  = -1;

    /**
     * Setting {@link #maxDepth} to 0 will have the same effect as using original {@link #ToStringStyle}: it will
     * print all 1st level values without traversing into them. Setting to 1 will traverse up to 2nd level and so
     * on.
     */
    private int                 maxDepth;

    private int                 depth;

    public RecursiveToStringStyle() {
        this(INFINITE_DEPTH);
    }

    public RecursiveToStringStyle(int maxDepth) {
        setUseShortClassName(true);
        setUseIdentityHashCode(false);

        this.maxDepth = maxDepth;
    }

    @Override
    protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
        if (value.getClass().getName().startsWith("java.lang.")
                    || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
            buffer.append(value);
        }
        else {
            depth++;
            buffer.append(ReflectionToStringBuilder.toString(value, this));
            depth--;
        }
    }

    // another helpful method
    @Override
    protected void appendDetail(StringBuffer buffer, String fieldName, Collection<?> coll) {
         depth++;
         buffer.append(ReflectionToStringBuilder.toString(coll.toArray(), this, true, true));
         depth--;
    }
}

8voto

Vadzim Punkte 22887

Hier eine modifizierte Version der Lösung von @dma_k mit Wiederverwendung eines einzelnen Puffers, Thread-Sicherheit, mehrzeiligem Einzug und Verwendung von Objekten toString Methode, wenn sie überschrieben wurde.

Beispielhafte Ausgabe:

ToStringTest.ParentStub {
    array = {a,b,c}
    map = {key2=null, key1=value1}
    child = ToStringTest.Stub {
        field1 = 12345
        field2 = Hello
        superField = abc
    }
    empty = <null>
    superField = abc
}

Code :

class RecursiveToStringStyle extends ToStringStyle {

    private static final RecursiveToStringStyle INSTANCE = new RecursiveToStringStyle(13);

    public static ToStringStyle getInstance() {
        return INSTANCE;
    }

    public static String toString(Object value) {
        final StringBuffer sb = new StringBuffer(512);
        INSTANCE.appendDetail(sb, null, value);
        return sb.toString();
    }

    private final int maxDepth;
    private final String tabs;

    // http://stackoverflow.com/a/16934373/603516
    private ThreadLocal<MutableInteger> depth = new ThreadLocal<MutableInteger>() {
        @Override
        protected MutableInteger initialValue() {
            return new MutableInteger(0);
        }
    };

    protected RecursiveToStringStyle(int maxDepth) {
        this.maxDepth = maxDepth;
        tabs = StringUtils.repeat("\t", maxDepth);

        setUseShortClassName(true);
        setUseIdentityHashCode(false);
        setContentStart(" {");
        setFieldSeparator(SystemUtils.LINE_SEPARATOR);
        setFieldSeparatorAtStart(true);
        setFieldNameValueSeparator(" = ");
        setContentEnd("}");
    }

    private int getDepth() {
        return depth.get().get();
    }

    private void padDepth(StringBuffer buffer) {
        buffer.append(tabs, 0, getDepth());
    }

    private StringBuffer appendTabified(StringBuffer buffer, String value) {
        //return buffer.append(String.valueOf(value).replace("\n", "\n" + tabs.substring(0, getDepth())));
        Matcher matcher = Pattern.compile("\n").matcher(value);
        String replacement = "\n" + tabs.substring(0, getDepth());
        while (matcher.find()) {
            matcher.appendReplacement(buffer, replacement);
        }
        matcher.appendTail(buffer);
        return buffer;
    }

    @Override
    protected void appendFieldSeparator(StringBuffer buffer) {
        buffer.append(getFieldSeparator());
        padDepth(buffer);
    }

    @Override
    public void appendStart(StringBuffer buffer, Object object) {
        depth.get().increment();
        super.appendStart(buffer, object);
    }

    @Override
    public void appendEnd(StringBuffer buffer, Object object) {
        super.appendEnd(buffer, object);
        buffer.setLength(buffer.length() - getContentEnd().length());
        buffer.append(SystemUtils.LINE_SEPARATOR);
        depth.get().decrement();
        padDepth(buffer);
        appendContentEnd(buffer);
    }

    @Override
    protected void removeLastFieldSeparator(StringBuffer buffer) {
        int len = buffer.length();
        int sepLen = getFieldSeparator().length() + getDepth();
        if (len > 0 && sepLen > 0 && len >= sepLen) {
            buffer.setLength(len - sepLen);
        }
    }

    private boolean noReflectionNeeded(Object value) {
        try {
            return value != null &&
                    (value.getClass().getName().startsWith("java.lang.")
                    || value.getClass().getMethod("toString").getDeclaringClass() != Object.class);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
        if (getDepth() >= maxDepth || noReflectionNeeded(value)) {
            appendTabified(buffer, String.valueOf(value));
        } else {
            new ReflectionToStringBuilder(value, this, buffer, null, false, false).toString();
        }
    }

    // another helpful method, for collections:
    @Override
    protected void appendDetail(StringBuffer buffer, String fieldName, Collection<?> coll) {
        buffer.append(ReflectionToStringBuilder.toString(coll.toArray(), this, true, true));
    }

    static class MutableInteger {
        private int value;
        MutableInteger(int value) { this.value = value; }
        public final int get() { return value; }
        public final void increment() { ++value; }
        public final void decrement() { --value; }
    }
}

2voto

Andreas Dolk Punkte 110776

Ich kenne keine Bibliothek auswendig, aber es ist ziemlich einfach mit Reflection Api und etwas Rekursion:

printMembers(Object instance) 
  foreach field
    if (field is primitive or String) // guess you're interested in the String value
       printPrimitive(field) 
    else if (field is array or collection)
       foreach item in field
          printmembers(item)
    else
       printmembers(field)            // no primitve, no array, no collection -> object

Das Abrufen aller Felder ist mit der Java Reflection API kein Problem. Wenn das Feld ein Array oder eine Instanz von Iterable verwenden Sie einfach den Iterator, um alle Array/Sammlungs-Handler zu erhalten.

Bei einer benutzerdefinierten Implementierung können Sie spezielle Handler für spezielle Objekte hinzufügen (z. B. Behandlung von String als Primitiv), um Unordnung in den Protokollen zu vermeiden.

1voto

Ben Asmussen Punkte 954

Apache commons-lang 3.10 hat die neue RecursiveToStringStyle .

ToStringBuilder.reflectionToString(table, new RecursiveToStringStyle());

Outout

Person@7f54[name=Stephen,age=29,smoker=false,job=Job@43cd2[title=Manager]]

0voto

Devon_C_Miller Punkte 16058

Dies ist etwas, das ich für meinen persönlichen Gebrauch geschrieben habe. Lassen Sie es mich wissen, wenn es hilft:

public static String arrayToString(final Object obj){
    if (obj == null) {
        return "<null>";
    }
    else {
        Object array = null;
        if (obj instanceof Collection) {
            array = ((Collection) obj).toArray();
        }
        else if (obj.getClass().isArray()) {
            array = obj;
        }
        else {
            return notNull(obj);
        }
        int length = Array.getLength(array);
        int lastItem = length - 1;
        StringBuffer sb = new StringBuffer("[");
        for (int i = 0; i < length; i++) {
            sb.append(arrayToString(Array.get(array, i)));
            if (i < lastItem) {
                sb.append(", ");
            }
        }
        sb.append(']');
        return sb.toString();
    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X