Java toString - ToStringBuilder not sufficient; won't traverse

前端 未结 7 1874
Happy的楠姐
Happy的楠姐 2020-12-07 23:18

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

相关标签:
7条回答
  • 2020-12-07 23:29

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

    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

    0 讨论(0)
  • 2020-12-07 23:31

    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();
        }
    }
    
    0 讨论(0)
  • 2020-12-07 23:32

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

    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 Iterable just use the iterator to get all array/collection handlers.

    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.

    0 讨论(0)
  • 2020-12-07 23:33

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

    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; }
        }
    }
    
    0 讨论(0)
  • 2020-12-07 23:36

    Extended above code for List and 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--;
            }
        }
    }}
    
    0 讨论(0)
  • 2020-12-07 23:37

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

    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--;
        }
    }
    
    0 讨论(0)
提交回复
热议问题