转储一个Java对象的属性

有一个库将recursion转储/打印对象的属性? 我正在寻找类似于Firebug中的console.dir()函数。

我知道commons-lang ReflectionToStringBuilder,但它不recursion到一个对象。 即,如果我运行以下:

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()方法,但在现实世界中,我正在处理我无法修改的外部对象。

你可以试试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原来的build议,但事实certificate,我想转储的对象图包括一个引用回到XStream marshaller本身,它并没有太热情(为什么它必须抛出一个exception,而不是忽略它或logging一个很好的警告,我不知道。)

然后我尝试了上面user519500的代码,但发现我需要一些调整。 这里有一个类可以放入一个项目中,它提供了以下额外的function:

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

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

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

如上所述,你需要小心堆栈溢出,所以使用最大recursion深度设施来降低风险。

希望有人会觉得这有用!

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

您可以使用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); 

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

本线程的Ben Williams的固定版本

注意:这个方法使用recursion,所以如果你有一个非常深的对象图,你可能会得到一个堆栈溢出(没有双关语意图;)如果你需要使用VM参数-Xss10m。 如果你使用eclipse把它放在运行>运行configuration>扩充(标签)虚拟增长框,然后按应用

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

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

  • 不使用任何外部库
  • 使用reflection来访问字段,包括超类字段
  • 使用recursion遍历对象图,每个调用只有一个堆栈帧
  • 使用IdentityHashMap来处理向后引用并避免无限recursion
  • 处理原语,自动装箱,CharSequences,枚举和适当的空值
  • 允许您select是否parsing静态字段
  • 很简单,可以根据格式设置进行修改

我写了下面的工具类:

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

我在一些课上进行了testing,对我来说非常有效。 例如,尝试使用它来转储主线程:

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

编辑

自写这篇文章,我有理由创造这个algorithm的迭代版本。 recursion版本的深度受到整个堆栈帧的限制,但您可能有理由转储非常大的对象图。 为了处理我的情况,我修改了algorithm来使用堆栈数据结构来代替运行时堆栈。 这个版本非常省时,并且受堆大小而不是堆栈帧深度的限制。

你可以在这里下载和使用迭代版本 。

你应该使用RecursiveToStringStyle:

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

也许你可以使用像XStream , Digester或JAXB这样的XML绑定框架。

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

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

我build议你使用Java的GSON Lib。

如果你使用Maven,你可以使用这个 。

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

这里举例说明如何使用它:

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

对于除String外的其他键的Map对象不起作用。 也许JsonConfig可以处理这个。