Java toString - ToStringBuilder 不够用;不会穿越

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3149951/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 16:53:03  来源:igfitidea点击:

Java toString - ToStringBuilder not sufficient; won't traverse

java

提问by BestPractices

I need to be able to traverse through my entire object graph and log all contents of all member fields.

我需要能够遍历整个对象图并记录所有成员字段的所有内容。

For example: Object A has a collection of Object B's which has a collection of Object C's and A, B, C have additional fields on them, etc.

例如:对象 A 有一个对象 B 的集合,而对象 B 有一个对象 C 的集合,而 A、B、C 在它们上面有额外的字段,等等。

Apache Commons ToStringBuilderis not sufficient since it won't traverse down an object graph or output contents of a collection.

Apache Commons ToStringBuilder是不够的,因为它不会向下遍历对象图或集合的输出内容。

Does anyone know of another library that will do this or have a code snippet that does this?

有谁知道另一个库可以做到这一点,或者有一个代码片段可以做到这一点?

采纳答案by dma_k

You can traverse the whole tree using org.apache.commons.lang.builder.ReflectionToStringBuilder. The trick is that in ToStringStyleyou need to traverse into the value. ToStringStylewill take care of values, already processed, and will not allow recursion. Here we go:

您可以使用 遍历整棵树org.apache.commons.lang.builder.ReflectionToStringBuilder。诀窍在于,ToStringStyle您需要遍历该值。ToStringStyle将处理已经处理的值,并且不允许递归。开始了:

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

回答by Andreas Dolk

I don't know a library by heart, but it's pretty easy with reflection api and some recursion:

我不知道一个库,但是使用反射 api 和一些递归很容易:

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

Getting all fields is not a problem with Java Reflection API. If the field is an array or an instance of Iterablejust use the iterator to get all array/collection handlers.

使用 Java Reflection API 获取所有字段不是问题。如果该字段是一个数组或一个实例,则Iterable只需使用迭代器来获取所有数组/集合处理程序。

With a custom implementation your free to add special handlers for special objects (like treating String as a primitive) to avoid clutter in the logs.

使用自定义实现,您可以自由地为特殊对象添加特殊处理程序(例如将 String 视为原语)以避免日志中的混乱。

回答by Devon_C_Miller

This is something I've written for my personal use. Let me know if it helps:

这是我写给我个人使用的东西。如果有帮助,请告诉我:

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

回答by BestPractices

This link ended up being a good starting point. You basically need something that's recursive but won't get lost in cyclic-references (Object A has a reference to Object B which has reference back to Object A; you dont want to get stuck traversing that over and over again).

这个链接最终成为一个很好的起点。你基本上需要一些递归但不会在循环引用中丢失的东西(对象 A 有一个对对象 B 的引用,而对象 B 又引用回对象 A;你不想被卡住一遍又一遍地遍历它)。

http://www.java2s.com/Code/Java/Class/Constructsprettystringrepresentationofobjectvalue.htm

http://www.java2s.com/Code/Java/Class/Constructsprettystringrepresentationofobjectvalue.htm

This was also somewhat helpful as well

这也有点帮助

http://binkley.blogspot.com/2007/08/recursive-tostring.html

http://binkley.blogspot.com/2007/08/recursive-tostring.html

回答by Vadzim

Here is a modified version of @dma_k's solution featuring single buffer reuse, thread safety, multi-line indent and use of object's toStringmethod if it has been overridden.

这是@dma_k 解决方案的修改版本,具有单缓冲区重用、线程安全、多行缩进和对象toString方法(如果已被覆盖)的使用。

Sample output:

示例输出:

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

回答by Milan

Extended above code for List and Map:

为 List 和 Map 扩展上面的代码:

Code :

代码 :

public class MultipleRecursiveToStringStyle extends ToStringStyle {
private static final int    INFINITE_DEPTH  = -1;

private int                 maxDepth;

private int                 depth;

public MultipleRecursiveToStringStyle() {
    this(INFINITE_DEPTH);
}

public MultipleRecursiveToStringStyle(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--;
    }
}

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

@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Map<?, ?> map) {
    TreeMap<?,?> sortedMap = new TreeMap<Object, Object>(map);
    for(Map.Entry<?,?> kvEntry: sortedMap.entrySet()){
        Object value = kvEntry.getKey();
        if (value.getClass().getName().startsWith("java.lang.")
                || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
            buffer.append(value);
        } else {
            depth++;
            buffer.append(ReflectionToStringBuilder.toString(value, this));
            depth--;
        }
        value = kvEntry.getValue();
        if (value.getClass().getName().startsWith("java.lang.")
                || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
            buffer.append(value);
        } else {
            depth++;
            buffer.append(ReflectionToStringBuilder.toString(value, this));
            depth--;
        }
    }
}}