Сбрасывание свойств объекта java
Есть ли библиотека, которая будет рекурсивно выгружать/печатать свойства объектов? Я ищу что-то похожее на console.dir() в Firebug.
Мне известно, что Commons-lang ReflectionToStringBuilder, но он не рекурсирует в объект. I.e., если я запускаю следующее:
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() для Inner, но в реальном мире я имею дело с внешними объектами, которые я не могу изменить.
Ответы
Ответ 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
И будьте осторожны с циркулярными ссылками;)
Ответ 2
Я попробовал использовать 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;
}
}
Ответ 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
это выведет все поля (включая массивы объектов) объекта.
Исправлена версия сообщения Ben Williams из этой темы
Примечание: этот метод использует рекурсию, поэтому, если у вас очень глубокий граф объектов, вы можете получить переполнение стека (без каламбура); ЕСЛИ так вам нужно использовать параметр VM -Xss10m. Если вы используете eclipse, поместите его в run > runconfiguration > 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
Мне хотелось найти элегантное решение этой проблемы:
- Не использует внешнюю библиотеку
- Использует Отражение для доступа к полям, включая поля суперкласса
- Использует рекурсию для перемещения объекта-графика только с одним стековым фреймом на вызов
- Использует 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 Qaru 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()));
}
Edit
С момента написания этого сообщения у меня возникла причина создать итеративную версию этого алгоритма. Рекурсивная версия ограничена по глубине полными фреймами стека, но у вас может быть причина сбрасывать чрезвычайно большой граф объектов. Чтобы справиться со своей ситуацией, я пересмотрел алгоритм использования структуры данных стека вместо стека времени выполнения. Эта версия эффективна по времени и ограничена размером кучи вместо глубины фрейма стека.
Вы можете скачать и использовать итеративную версию здесь.
Ответ 6
Может быть, вы можете использовать фреймворк привязки XML, например XStream, Digester или JAXB для этого.
Ответ 7
Вы должны использовать RecursiveToStringStyle:
System.out.println(ReflectionToStringBuilder.toString(new Outer(), new RecursiveToStringStyle()));
Ответ 8
Вы можете использовать Gson для представления вашего объекта в формате json:
new GsonBuilder().setPrettyPrinting().create().toJson(yourObject);
Ответ 9
Я рекомендую вам использовать GSON Lib fo Java.
Если вы используете Maven, вы можете использовать this.
Или вы можете скачать Jar файл из здесь.
Здесь пример использования:
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String json = gson.toJson(obj);
System.out.println(json);
Ответ 10
JSONObject.fromObject(value)
Не работает для объектов Map с другими ключами, кроме String. Возможно, JsonConfig может справиться с этим.