当前位置:  开发笔记 > 编程语言 > 正文

转储java对象的属性

如何解决《转储java对象的属性》经验,为你挑选了5个好方法。

是否有一个库将以递归方式转储/打印对象属性?我正在寻找类似于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()方法,但在现实世界中,我正在处理我无法修改的外部对象.



1> cherouvim..:

你可以试试XStream.

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

打印出来:


  5
  
    foo
  

您也可以输出JSON

并注意循环引用;)



2> John Rix..:

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

然后我尝试了上面的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 ignoreList = new HashMap();
        HashMap visited = new HashMap();
    }

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

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

        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("");
                            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 "";
        }
        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 "";
                }
            }
            else {
                return "";
            }
        }
    }


    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`立即转换为String(没有进一步向下钻取).有用的补充是:`value.getClass()== java.util.Date.class`,`value.getClass().isEnum()`等.你通常不会对这类对象的内部感兴趣,仅在String表示中.

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


这是另一个版本的自定义ToStringStyle http://stackoverflow.com/questions/3149951/java-tostring-tostringbuilder-not-sufficient-wont-traverse/3514475#3514475

4> 小智..:

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

Ben Williams的固定版本来自这个帖子

注意:此方法使用递归,因此如果您有一个非常深的对象图,您可能会得到堆栈溢出(没有双关语;)因此您需要使用VM参数-Xss10m.如果你使用eclipse将它放入run> runco​​nfiguration> augments(tab)VM扩充框并按下apply

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



5> Bryan W. Wag..:

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

不使用任何外部库

使用Reflection来访问字段,包括超类字段

使用递归遍历Object-graph,每次调用只有一个堆栈帧

使用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.

* @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(), 0); } /** * Uses reflection and recursion to dump the contents of the given object using a custom, JSON-like notation (but not JSON).

* Parses all fields of the runtime class including super class fields, which are successively prefixed with "{@code super.}" at each level.

* {@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.

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

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

* * @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(), 0); } private static String dump(Object object, boolean isIncludingStatics, IdentityHashMap 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 fieldMap = new TreeMap(); // 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 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()));
}

编辑

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

您可以在此处下载并使用迭代版本.

推荐阅读
地之南_816
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有