reflexion - Volcar las propiedades de un objeto java
reflexion java example (10)
Deberías usar RecursiveToStringStyle:
System.out.println(ReflectionToStringBuilder.toString(new Outer(), new RecursiveToStringStyle()));
¿Hay una biblioteca que volcará / imprimirá recursivamente las propiedades de un objeto? Estoy buscando algo similar a la función console.dir() en Firebug.
Soy consciente del common-lang ReflectionToStringBuilder pero no se repite en un objeto. Es decir, si ejecuto lo siguiente:
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";
}
}
Recibo:
ToString $ Outer @ 1b67f74 [intValue = 5
innerValue = ToString $ Inner @ 530daa]
Me doy cuenta de que en mi ejemplo, podría haber anulado el método toString () para Inner, pero en el mundo real, estoy tratando con objetos externos que no puedo modificar.
Intenté usar XStream como se sugirió originalmente, pero resulta que el gráfico de objetos que quería volcar incluía una referencia de regreso al marshaller XStream en sí mismo, que no tuvo en cuenta muy amablemente (por qué debe lanzar una excepción en lugar de ignorarla o registrando una buena advertencia, no estoy seguro.)
Luego probé el código del usuario519500 anterior, pero descubrí que necesitaba algunos ajustes. Aquí hay una clase en la que puede participar en un proyecto que ofrece las siguientes características adicionales:
- Puede controlar la profundidad máxima de recursión
- Puede limitar la salida de elementos de la matriz
- Puede ignorar cualquier lista de clases, campos o combinaciones de clase + campo: simplemente pase una matriz con cualquier combinación de nombres de clase, nombre de clase + nombre de campo separados por dos puntos, o nombres de campo con un prefijo de dos puntos, es decir:
[<classname>][:<fieldname>]
- No generará el mismo objeto dos veces (la salida indica cuando un objeto fue visitado previamente y proporciona el código hash para la correlación) - esto evita las referencias circulares que causan problemas
Puede llamar esto usando uno de los dos métodos a continuación:
String dump = Dumper.dump(myObject);
String dump = Dumper.dump(myObject, maxDepth, maxArrayElements, ignoreList);
Como se mencionó anteriormente, debe tener cuidado con los desbordamientos de pila con esto, así que use la función de profundidad de recursión máxima para minimizar el riesgo.
¡Espero que alguien lo encuentre útil!
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;
}
}
Podrías probar con XStream .
XStream xstream = new XStream(new Sun14ReflectionProvider(
new FieldDictionary(new ImmutableFieldKeySorter())),
new DomDriver("utf-8"));
System.out.println(xstream.toXML(new Outer()));
imprime:
<foo.ToString_-Outer>
<intValue>5</intValue>
<innerValue>
<stringValue>foo</stringValue>
</innerValue>
</foo.ToString_-Outer>
También podría generar en JSON
Y tenga cuidado con las referencias circulares;)
Puede usar Gson para representar su objeto en formato json:
new GsonBuilder().setPrettyPrinting().create().toJson(yourObject);
Puede usar ReflectionToStringBuilder con un ToStringStyle personalizado, por ejemplo:
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());
}
}
Y luego lo invocas como:
ReflectionToStringBuilder.toString(value, MyStyle.getInstance());
Sin embargo, ¡ten cuidado con las referencias circulares!
También puede usar json-lib ( http://json-lib.sourceforge.net ) y simplemente hacer:
JSONObject.fromObject(value);
Quería una solución elegante a este problema que:
- No usa ninguna biblioteca externa
- Utiliza Reflection para acceder a los campos, incluidos los campos de superclase
- Utiliza recursividad para atravesar el Object-graph con solo un marco de pila por llamada
- Utiliza un IdentityHashMap para manejar las referencias hacia atrás y evitar la recursión infinita
- Maneja primitivas, auto-boxing, CharSequences, enumeraciones y nulos apropiadamente
- Le permite elegir si desea analizar o no los campos estáticos
- Es lo suficientemente simple como para modificar de acuerdo con las preferencias de formato
Escribí la siguiente clase de utilidad:
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 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();
}
}
Lo probé en varias clases y para mí es extremadamente eficiente. Por ejemplo, intente usarlo para volcar el hilo principal:
public static void main(String[] args) throws Exception {
System.out.println(dump(Thread.currentThread()));
}
Editar
Desde que escribí esta publicación tuve razones para crear una versión iterativa de este algoritmo. La versión recursiva está limitada en profundidad por los cuadros de pila totales, pero es posible que tenga motivos para volcar un gráfico de objetos extremadamente grande. Para manejar mi situación, revisé el algoritmo para usar una estructura de datos de pila en lugar de la pila de tiempo de ejecución. Esta versión es eficiente en tiempo y está limitada por el tamaño del montón en lugar de la profundidad del marco de la pila.
Puede descargar y usar la versión iterativa aquí .
esto imprimirá todos los campos (incluidas las matrices de objetos) de un objeto.
Versión fija de la publicación de Ben Williams de este hilo
Nota: este método usa la recursividad por lo que si tiene un gráfico de objeto muy profundo puede obtener un desbordamiento de pila (sin juego de palabras;) SI es así, debe usar el parámetro VM -Xss10m. Si su eclipse de uso lo puso en ejecución> configuración de ejecución> aumenta el cuadro de aumento VM y presione aplicar
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();
}
JSONObject.fromObject(value)
No funciona para objetos Map con otras teclas que String. Quizás JsonConfig puede manejar esto.