有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

反射转储java对象的属性

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

我知道commons langReflectionToStringBuilder,但它不会递归到对象中。即,如果我运行以下操作:

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 ]

我意识到在我的示例中,我可以重写内部的toString()方法,但在现实世界中,我处理的是无法修改的外部对象


共 (6) 个答案

  1. # 1 楼答案

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

    修正了来自this thread的Ben Williams post版本

    注意:这个方法使用递归,所以如果你有一个非常深的对象图,你可能会得到堆栈溢出(没有双关语;)如果是这样,您需要使用VM参数-Xss10m。如果您使用eclipse将其置于运行状态>;运行配置>;增强(选项卡)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();
    }
    
  2. # 2 楼答案

    您应该使用RecursiveToString样式:

    System.out.println(ReflectionToStringBuilder.toString(new Outer(), new RecursiveToStringStyle()));
    
  3. # 3 楼答案

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

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

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

    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

  4. # 4 楼答案

    你可以试试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中输出

    注意循环引用;)

  5. # 5 楼答案

    我试着按照最初的建议使用XStream,但结果是我想要转储的对象图包含了一个对XStream封送器本身的引用,对此我并不太乐意(我不确定为什么它必须抛出一个异常,而不是忽略它或记录一个好的警告)

    然后我尝试了上面user519500的代码,但发现我需要一些调整。以下是一个类,您可以将其应用到提供以下额外功能的项目中:

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

    您可以使用以下两种方法之一调用此函数:

        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;
        }
    }
    
  6. # 6 楼答案

    您可以将ReflectionStringBuilder与自定义ToString样式一起使用,例如:

    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库(http://json-lib.sourceforge.net),只需执行以下操作:

    JSONObject.fromObject(value);