【问题标题】:Dumping a java object's properties转储 java 对象的属性
【发布时间】:2010-10-10 19:42:54
【问题描述】:

是否有一个库可以递归地转储/打印对象属性?我正在寻找类似于 Firebug 中的 console.dir() 函数的东西。

我知道 commons-lang ReflectionToStringBuilder 但它不会递归到对象中。即,如果我运行以下命令:

public class ToString {

    public static void main(String [] args) {
        System.out.println(ReflectionToStringBuilder.toString(new Outer(), ToStringStyle.MULTI_LINE_STYLE));
    }

    private static class Outer {
        private int intValue = 5;
        private Inner innerValue = new Inner();
    }

    private static class Inner {
        private String stringValue = "foo";
    }
}

我收到:

ToString$Outer@1b67f74[ intValue=5
innerValue=ToString$Inner@530daa ]

我意识到,在我的示例中,我可以覆盖 Inner 的 toString() 方法,但在现实世界中,我正在处理我无法修改的外部对象。

【问题讨论】:

    标签: java reflection properties dump tostring


    【解决方案1】:

    你可以试试XStream

    XStream xstream = new XStream(new Sun14ReflectionProvider(
      new FieldDictionary(new ImmutableFieldKeySorter())),
      new DomDriver("utf-8"));
    System.out.println(xstream.toXML(new Outer()));
    

    打印出来:

    <foo.ToString_-Outer>
      <intValue>5</intValue>
      <innerValue>
        <stringValue>foo</stringValue>
      </innerValue>
    </foo.ToString_-Outer>
    

    你也可以在JSON输出

    注意循环引用;)

    【讨论】:

    • 不错。我现在觉得很傻,因为我经常使用 XStream 而我什至没有想到它。
    • 这适用于枚举值吗?我相信(默认)使用 XMLEncoder.writeObject 和休眠的 XML 序列化在序列化枚举值/类型方面存在一些问题。
    • @extraneon:抱歉,我对 Java 1.5(枚举等)没有太多经验
    【解决方案2】:

    也许您可以为此使用像 XStreamDigesterJAXB 这样的 XML 绑定框架。

    【讨论】:

      【解决方案3】:

      您可以将 ReflectionToStringBuilder 与自定义 ToStringStyle 一起使用,例如:

      class MyStyle extends ToStringStyle {
          private final static ToStringStyle instance = new MyStyle();
      
          public MyStyle() {
              setArrayContentDetail(true);
              setUseShortClassName(true);
              setUseClassName(false);
              setUseIdentityHashCode(false);
              setFieldSeparator(", " + SystemUtils.LINE_SEPARATOR + "  ");
          }
      
          public static ToStringStyle getInstance() {
              return instance;
          };
      
          @Override
          public void appendDetail(StringBuffer buffer, String fieldName, Object value) {
              if (!value.getClass().getName().startsWith("java")) {
                  buffer.append(ReflectionToStringBuilder.toString(value, instance));
              } else {
                  super.appendDetail(buffer, fieldName, value);
              }
          }
      
          @Override
          public void appendDetail(StringBuffer buffer, String fieldName, Collection value) {
              appendDetail(buffer, fieldName, value.toArray());
          }
      }
      

      然后你像这样调用它:

      ReflectionToStringBuilder.toString(value, MyStyle.getInstance());
      

      注意循环引用!


      您也可以使用 json-lib (http://json-lib.sourceforge.net) 并执行以下操作:

      JSONObject.fromObject(value);
      

      【讨论】:

      【解决方案4】:
      JSONObject.fromObject(value)
      

      不适用于具有除 String 之外的其他键的 Map 对象。也许 JsonConfig 可以处理这个。

      【讨论】:

        【解决方案5】:

        这将打印出一个对象的所有字段(包括对象数组)。

        来自 this thread 的 Ben Williams 帖子的固定版本

        注意:这个方法使用递归,所以如果你有一个非常深的对象图,你可能会得到一个堆栈溢出(没有双关语;)如果你需要使用 VM 参数 -Xss10m。如果你使用 eclipse 把它放在 run>runco​​nfiguration>augments (tab) VM 扩充框中,然后按应用

        import java.lang.reflect.Array;
        import java.lang.reflect.Field;
        
        public static String dump(Object o) {
            StringBuffer buffer = new StringBuffer();
            Class oClass = o.getClass();
             if (oClass.isArray()) {
                 buffer.append("Array: ");
                buffer.append("[");
                for (int i = 0; i < Array.getLength(o); i++) {
                    Object value = Array.get(o, i);
                    if (value.getClass().isPrimitive() ||
                            value.getClass() == java.lang.Long.class ||
                            value.getClass() == java.lang.Integer.class ||
                            value.getClass() == java.lang.Boolean.class ||
                            value.getClass() == java.lang.String.class ||
                            value.getClass() == java.lang.Double.class ||
                            value.getClass() == java.lang.Short.class ||
                            value.getClass() == java.lang.Byte.class
                            ) {
                        buffer.append(value);
                        if(i != (Array.getLength(o)-1)) buffer.append(",");
                    } else {
                        buffer.append(dump(value));
                     }
                }
                buffer.append("]\n");
            } else {
                 buffer.append("Class: " + oClass.getName());
                 buffer.append("{\n");
                while (oClass != null) {
                    Field[] fields = oClass.getDeclaredFields();
                    for (int i = 0; i < fields.length; i++) {
                        fields[i].setAccessible(true);
                        buffer.append(fields[i].getName());
                        buffer.append("=");
                        try {
                            Object value = fields[i].get(o);
                            if (value != null) {
                                if (value.getClass().isPrimitive() ||
                                        value.getClass() == java.lang.Long.class ||
                                        value.getClass() == java.lang.String.class ||
                                        value.getClass() == java.lang.Integer.class ||
                                        value.getClass() == java.lang.Boolean.class ||
                                            value.getClass() == java.lang.Double.class ||
                                        value.getClass() == java.lang.Short.class ||
                                        value.getClass() == java.lang.Byte.class
                                        ) {
                                    buffer.append(value);
                                } else {
                                    buffer.append(dump(value));
                                }
                            }
                        } catch (IllegalAccessException e) {
                            buffer.append(e.getMessage());
                        }
                        buffer.append("\n");
                    }
                    oClass = oClass.getSuperclass();
                }
                buffer.append("}\n");
            }
            return buffer.toString();
        }
        

        【讨论】:

        • 您能具体将您的代码发布到公共领域吗?
        • 您忘记在此代码中包含一个非常重要的部分import java.lang.reflect.Array; import java.lang.reflect.Field;(我刚刚研究了如何编辑您的帖子以添加它)
        【解决方案6】:

        我尝试按照最初的建议使用 XStream,但事实证明我想要转储的对象图包含一个对 XStream 编组器本身的引用,它并不太友好(为什么它必须抛出异常而不是忽略它或记录一个很好的警告,我不确定。)

        然后我尝试了上面 user519500 的代码,但发现我需要一些调整。您可以将以下课程加入到提供以下额外功能的项目中:

        • 可以控制最大递归深度
        • 可以限制数组元素输出
        • 可以忽略任何类、字段或类+字段组合的列表 - 只需传递一个数组,其中包含类名的任意组合、以冒号分隔的类名+字段名对或带有冒号前缀的字段名,即:[&lt;classname&gt;][:&lt;fieldname&gt;]
        • 不会两次输出相同的对象(输出指示对象以前被访问的时间并提供相关的哈希码) - 这避免了导致问题的循环引用

        您可以使用以下两种方法之一调用它:

            String dump = Dumper.dump(myObject);
            String dump = Dumper.dump(myObject, maxDepth, maxArrayElements, ignoreList);
        

        如上所述,您需要注意堆栈溢出,因此请使用最大递归深度工具将风险降至最低。

        希望有人会觉得这很有用!

        package com.mycompany.myproject;
        
        import java.lang.reflect.Array;
        import java.lang.reflect.Field;
        import java.util.HashMap;
        
        public class Dumper {
            private static Dumper instance = new Dumper();
        
            protected static Dumper getInstance() {
                return instance;
            }
        
            class DumpContext {
                int maxDepth = 0;
                int maxArrayElements = 0;
                int callCount = 0;
                HashMap<String, String> ignoreList = new HashMap<String, String>();
                HashMap<Object, Integer> visited = new HashMap<Object, Integer>();
            }
        
            public static String dump(Object o) {
                return dump(o, 0, 0, null);
            }
        
            public static String dump(Object o, int maxDepth, int maxArrayElements, String[] ignoreList) {
                DumpContext ctx = Dumper.getInstance().new DumpContext();
                ctx.maxDepth = maxDepth;
                ctx.maxArrayElements = maxArrayElements;
        
                if (ignoreList != null) {
                    for (int i = 0; i < Array.getLength(ignoreList); i++) {
                        int colonIdx = ignoreList[i].indexOf(':');
                        if (colonIdx == -1)
                            ignoreList[i] = ignoreList[i] + ":";
                        ctx.ignoreList.put(ignoreList[i], ignoreList[i]);
                    }
                }
        
                return dump(o, ctx);
            }
        
            protected static String dump(Object o, DumpContext ctx) {
                if (o == null) {
                    return "<null>";
                }
        
                ctx.callCount++;
                StringBuffer tabs = new StringBuffer();
                for (int k = 0; k < ctx.callCount; k++) {
                    tabs.append("\t");
                }
                StringBuffer buffer = new StringBuffer();
                Class oClass = o.getClass();
        
                String oSimpleName = getSimpleNameWithoutArrayQualifier(oClass);
        
                if (ctx.ignoreList.get(oSimpleName + ":") != null)
                    return "<Ignored>";
        
                if (oClass.isArray()) {
                    buffer.append("\n");
                    buffer.append(tabs.toString().substring(1));
                    buffer.append("[\n");
                    int rowCount = ctx.maxArrayElements == 0 ? Array.getLength(o) : Math.min(ctx.maxArrayElements, Array.getLength(o));
                    for (int i = 0; i < rowCount; i++) {
                        buffer.append(tabs.toString());
                        try {
                            Object value = Array.get(o, i);
                            buffer.append(dumpValue(value, ctx));
                        } catch (Exception e) {
                            buffer.append(e.getMessage());
                        }
                        if (i < Array.getLength(o) - 1)
                            buffer.append(",");
                        buffer.append("\n");
                    }
                    if (rowCount < Array.getLength(o)) {
                        buffer.append(tabs.toString());
                        buffer.append(Array.getLength(o) - rowCount + " more array elements...");
                        buffer.append("\n");
                    }
                    buffer.append(tabs.toString().substring(1));
                    buffer.append("]");
                } else {
                    buffer.append("\n");
                    buffer.append(tabs.toString().substring(1));
                    buffer.append("{\n");
                    buffer.append(tabs.toString());
                    buffer.append("hashCode: " + o.hashCode());
                    buffer.append("\n");
                    while (oClass != null && oClass != Object.class) {
                        Field[] fields = oClass.getDeclaredFields();
        
                        if (ctx.ignoreList.get(oClass.getSimpleName()) == null) {
                            if (oClass != o.getClass()) {
                                buffer.append(tabs.toString().substring(1));
                                buffer.append("  Inherited from superclass " + oSimpleName + ":\n");
                            }
        
                            for (int i = 0; i < fields.length; i++) {
        
                                String fSimpleName = getSimpleNameWithoutArrayQualifier(fields[i].getType());
                                String fName = fields[i].getName();
        
                                fields[i].setAccessible(true);
                                buffer.append(tabs.toString());
                                buffer.append(fName + "(" + fSimpleName + ")");
                                buffer.append("=");
        
                                if (ctx.ignoreList.get(":" + fName) == null &&
                                    ctx.ignoreList.get(fSimpleName + ":" + fName) == null &&
                                    ctx.ignoreList.get(fSimpleName + ":") == null) {
        
                                    try {
                                        Object value = fields[i].get(o);
                                        buffer.append(dumpValue(value, ctx));
                                    } catch (Exception e) {
                                        buffer.append(e.getMessage());
                                    }
                                    buffer.append("\n");
                                }
                                else {
                                    buffer.append("<Ignored>");
                                    buffer.append("\n");
                                }
                            }
                            oClass = oClass.getSuperclass();
                            oSimpleName = oClass.getSimpleName();
                        }
                        else {
                            oClass = null;
                            oSimpleName = "";
                        }
                    }
                    buffer.append(tabs.toString().substring(1));
                    buffer.append("}");
                }
                ctx.callCount--;
                return buffer.toString();
            }
        
            protected static String dumpValue(Object value, DumpContext ctx) {
                if (value == null) {
                    return "<null>";
                }
                if (value.getClass().isPrimitive() ||
                    value.getClass() == java.lang.Short.class ||
                    value.getClass() == java.lang.Long.class ||
                    value.getClass() == java.lang.String.class ||
                    value.getClass() == java.lang.Integer.class ||
                    value.getClass() == java.lang.Float.class ||
                    value.getClass() == java.lang.Byte.class ||
                    value.getClass() == java.lang.Character.class ||
                    value.getClass() == java.lang.Double.class ||
                    value.getClass() == java.lang.Boolean.class ||
                    value.getClass() == java.util.Date.class ||
                    value.getClass().isEnum()) {
        
                    return value.toString();
        
                } else {
        
                    Integer visitedIndex = ctx.visited.get(value);
                    if (visitedIndex == null) {
                        ctx.visited.put(value, ctx.callCount);
                        if (ctx.maxDepth == 0 || ctx.callCount < ctx.maxDepth) {
                            return dump(value, ctx);
                        }
                        else {
                            return "<Reached max recursion depth>";
                        }
                    }
                    else {
                        return "<Previously visited - see hashCode " + value.hashCode() + ">";
                    }
                }
            }
        
        
            private static String getSimpleNameWithoutArrayQualifier(Class clazz) {
                String simpleName = clazz.getSimpleName();
                int indexOfBracket = simpleName.indexOf('['); 
                if (indexOfBracket != -1)
                    return simpleName.substring(0, indexOfBracket);
                return simpleName;
            }
        }
        

        【讨论】:

        • 谢谢,效果很好(竖起大拇指)!我对我的进行了一些小的更改以消除混乱,即在dumpValue() 中,我扩展了将导致value 立即转换为字符串的条件列表(不再深入研究)。有用的补充是:value.getClass() == java.util.Date.classvalue.getClass().isEnum() 等。您通常对这些对象的内部不感兴趣,只对字符串表示形式感兴趣。
        • 您能具体将您的代码发布到公共领域吗?
        • 如果这只是我这样说的一个例子,那么我正式将上述代码发布到公共领域(没有暗示保证和所有类似的东西)。
        • 谢谢你正在寻找类似的东西。节省了我的时间:)
        • 根据@CornelMasson 的建议在 dumpValue 中添加了额外的条件
        【解决方案7】:

        我想要一个优雅的解决方案来解决这个问题:

        • 不使用任何外部库
        • 使用Reflection 访问字段,包括超类字段
        • 使用递归遍历对象图,每次调用只有一个堆栈帧
        • 使用IdentityHashMap 处理向后引用并避免无限递归
        • 适当地处理基元、自动装箱、CharSequences、枚举和空值
        • 允许您选择是否解析静态字段
        • 足够简单,可以根据格式偏好进行修改

        我编写了以下实用程序类:

        import java.lang.reflect.Array;
        import java.lang.reflect.Field;
        import java.lang.reflect.Modifier;
        import java.util.IdentityHashMap;
        import java.util.Map.Entry;
        import java.util.TreeMap;
        
        /**
         * Utility class to dump {@code Object}s to string using reflection and recursion.
         */
        public class StringDump {
        
            /**
             * Uses reflection and recursion to dump the contents of the given object using a custom, JSON-like notation (but not JSON). Does not format static fields.<p>
             * @see #dump(Object, boolean, IdentityHashMap, int)
             * @param object the {@code Object} to dump using reflection and recursion
             * @return a custom-formatted string representing the internal values of the parsed object
             */
            public static String dump(Object object) {
                return dump(object, false, new IdentityHashMap<Object, Object>(), 0);
            }
        
            /**
             * Uses reflection and recursion to dump the contents of the given object using a custom, JSON-like notation (but not JSON).<p>
             * Parses all fields of the runtime class including super class fields, which are successively prefixed with "{@code super.}" at each level.<p>
             * {@code Number}s, {@code enum}s, and {@code null} references are formatted using the standard {@link String#valueOf()} method.
             * {@code CharSequences}s are wrapped with quotes.<p>
             * The recursive call invokes only one method on each recursive call, so limit of the object-graph depth is one-to-one with the stack overflow limit.<p>
             * Backwards references are tracked using a "visitor map" which is an instance of {@link IdentityHashMap}.
             * When an existing object reference is encountered the {@code "sysId"} is printed and the recursion ends.<p>
             * 
             * @param object             the {@code Object} to dump using reflection and recursion
             * @param isIncludingStatics {@code true} if {@code static} fields should be dumped, {@code false} to skip them
             * @return a custom-formatted string representing the internal values of the parsed object
             */
            public static String dump(Object object, boolean isIncludingStatics) {
                return dump(object, isIncludingStatics, new IdentityHashMap<Object, Object>(), 0);
            }
        
            private static String dump(Object object, boolean isIncludingStatics, IdentityHashMap<Object, Object> visitorMap, int tabCount) {
                if (object == null ||
                        object instanceof Number || object instanceof Character || object instanceof Boolean ||
                        object.getClass().isPrimitive() || object.getClass().isEnum()) {
                    return String.valueOf(object);
                }
        
                StringBuilder builder = new StringBuilder();
                int           sysId   = System.identityHashCode(object);
                if (object instanceof CharSequence) {
                    builder.append("\"").append(object).append("\"");
                }
                else if (visitorMap.containsKey(object)) {
                    builder.append("(sysId#").append(sysId).append(")");
                }
                else {
                    visitorMap.put(object, object);
        
                    StringBuilder tabs = new StringBuilder();
                    for (int t = 0; t < tabCount; t++) {
                        tabs.append("\t");
                    }
                    if (object.getClass().isArray()) {
                        builder.append("[").append(object.getClass().getName()).append(":sysId#").append(sysId);
                        int length = Array.getLength(object);
                        for (int i = 0; i < length; i++) {
                            Object arrayObject = Array.get(object, i);
                            String dump        = dump(arrayObject, isIncludingStatics, visitorMap, tabCount + 1);
                            builder.append("\n\t").append(tabs).append("\"").append(i).append("\":").append(dump);
                        }
                        builder.append(length == 0 ? "" : "\n").append(length == 0 ? "" : tabs).append("]");
                    }
                    else {
                        // enumerate the desired fields of the object before accessing
                        TreeMap<String, Field> fieldMap    = new TreeMap<String, Field>();  // can modify this to change or omit the sort order
                        StringBuilder          superPrefix = new StringBuilder();
                        for (Class<?> clazz = object.getClass(); clazz != null && !clazz.equals(Object.class); clazz = clazz.getSuperclass()) {
                            Field[] fields = clazz.getDeclaredFields();
                            for (int i = 0; i < fields.length; i++) {
                                Field field = fields[i];
                                if (isIncludingStatics || !Modifier.isStatic(field.getModifiers())) {
                                    fieldMap.put(superPrefix + field.getName(), field);
                                }
                            }
                            superPrefix.append("super.");
                        }
        
                        builder.append("{").append(object.getClass().getName()).append(":sysId#").append(sysId);
                        for (Entry<String, Field> entry : fieldMap.entrySet()) {
                            String name  = entry.getKey();
                            Field  field = entry.getValue();
                            String dump;
                            try {
                                boolean wasAccessible = field.isAccessible();
                                field.setAccessible(true);
                                Object  fieldObject   = field.get(object);
                                field.setAccessible(wasAccessible);  // the accessibility flag should be restored to its prior ClassLoader state
                                dump                  = dump(fieldObject, isIncludingStatics, visitorMap, tabCount + 1);
                            }
                            catch (Throwable e) {
                                dump = "!" + e.getClass().getName() + ":" + e.getMessage();
                            }
                            builder.append("\n\t").append(tabs).append("\"").append(name).append("\":").append(dump);
                        }
                        builder.append(fieldMap.isEmpty() ? "" : "\n").append(fieldMap.isEmpty() ? "" : tabs).append("}");
                    }
                }
                return builder.toString();
            }
        }
        

        我在多个课程中对其进行了测试,对我来说它非常有效。例如,尝试使用它来转储主线程:

        public static void main(String[] args) throws Exception {
            System.out.println(dump(Thread.currentThread()));
        }
        

        编辑

        自从写这篇文章以来,我有理由创建这个算法的迭代版本。递归版本的深度受限于总堆栈帧,但您可能有理由转储一个非常大的对象图。为了处理我的情况,我修改了算法以使用堆栈数据结构代替运行时堆栈。此版本具有时间效率,并且受堆大小而不是堆栈帧深度的限制。

        您可以下载并使用iterative version here

        【讨论】:

        • 很好,但我不得不对转储递归 6500 施加硬限制,因为这会导致 stackoverflow
        • 自从写了这篇文章,我随后写了一个(非递归的)迭代版本来解决同样的问题!我将在我的文件中查看代码并通过 Github 链接,以便您进行比较。
        • 呵呵我正要问这个,但我没有时间猜测是否可能,很酷,谢谢:)
        • @AquariusPower 我编辑了我的答案以提供迭代版本的链接。我需要它来转储一个非常大的对象图。希望对您有所帮助!
        • 很高兴它有点帮助!这是一个完整的对象图转储,因此它将探索所有可能的图边。尝试使用 JVM 参数 -Xmx1024m 或 -Xmx2048m 启动程序以增加堆大小(而不是捕获 OutOfMemoryError)。如果您需要比这更大的转储,您可以修改它以写入 FileOutputStream,而不是附加到 StringBuilder。
        【解决方案8】:

        你应该使用 RecursiveToStringStyle:

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

        【讨论】:

        • 需要 apache,但听起来很简单
        【解决方案9】:

        您可以使用Gson 以 json 格式表示您的对象:

        new GsonBuilder().setPrettyPrinting().create().toJson(yourObject);
        

        【讨论】:

          【解决方案10】:

          我建议你使用GSON Lib fo Java。

          如果你使用 Maven,你可以使用this

          或者你可以从here下载Jar文件。

          这里举例说明如何使用:

          Gson gson = new GsonBuilder().setPrettyPrinting().create();
          String json = gson.toJson(obj);
          System.out.println(json);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-09-13
            • 1970-01-01
            • 2016-07-30
            • 2020-02-09
            • 2021-01-28
            • 1970-01-01
            • 1970-01-01
            • 2018-10-14
            相关资源
            最近更新 更多