2009-03-02 5 views
62

Есть ли библиотека, которая будет рекурсивно выгружать/распечатывать свойства объектов? Я ищу что-то похожее на функцию console.dir() в Firebug.Сбрасывание свойств объекта java

Я знаю о 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 ]

Я понимаю, что в моем примере я мог бы переопределить метод toString() для Inner, но в реальном мире я имею дело с внешними объектами, которые я не могу изменить.

ответ

41

Вы можете попробовать 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

И быть осторожными круговыми ссылок;)

+0

Ницца. Теперь я чувствую себя глупо, потому что много использую XStream, и я даже не думал об этом. – Kevin

+0

Это работает с значениями перечисления? Я полагаю (по умолчанию) Сериализация XML с использованием XMLEncoder.writeObject и hibernate имеет некоторые проблемы с сериализацией значений/типов перечисления. – extraneon

+0

@extraneon: извините, у меня нет большого опыта работы с Java 1.5 (перечисления и т. Д.) – cherouvim

4

Может быть, вы могли бы использовать XML связывающую структуру как XStream, Digester или JAXB.

20

Вы можете использовать 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); 
+2

вот еще одна версия пользовательского ToStringStyle http://stackoverflow.com/questions/3149951/java-tostring-tostringbuilder-not-equective -wont-traverse/3514475 # 3514475 – remipod

+0

@ Ссылка remipod говорит: «ToStringStyle позаботится о значениях, уже обработанных и не позволит рекурсии» – Vadzim

+0

Похоже, что «ToStringStyle» имеет встроенную защиту от циклических ссылок: http://grepcode.com/file/repo1.maven.org/maven2/commons-lang/commons-lang/2.6/org/apache/commons/lang/builder/ToStringStyle.java#135 – Vadzim

0
JSONObject.fromObject(value) 

Не работает для объектов карты с другими клавишами, чем String. Возможно, JsonConfig может справиться с этим.

13

это распечатает все поля (включая массивы объектов) объекта.

Фиксированная версия Бен Уильямс пост от this thread

Примечания: этот метод использует рекурсию, так что если у вас есть очень глубокий граф объектов вы можете получить стек переполнение (не каламбур;) В этом случае вы должны использовать параметр VM -Xss10m.Если с помощью затмения положить его в перспективе> runconfiguration> дополняющие (вкладка) Augment окно 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(); 
} 
+0

Можете ли вы конкретно отпустить свой код в Pub Домен, довольно, пожалуйста? – stolsvik

+1

Вы забыли включить ДЕЙСТВИТЕЛЬНО важную часть этого кода 'import java.lang.reflect.Array; import java.lang.reflect.Field; ' (Я только что разработал, как отредактировать сообщение, чтобы добавить его) – Evildonald

35

Я попытался с помощью 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; 
    } 
} 
+4

Спасибо, хорошо работает (палец вверх)! Я сделал небольшие изменения для моего, чтобы устранить беспорядок, т. Е. В 'dumpValue()' Я расширил список условий, которые приведут к тому, что 'value' будет немедленно преобразован в строку (без дальнейшего сверления). Полезными дополнениями являются: 'value.getClass() == java.util.Date.class',' value.getClass(). IsEnum() 'и т. Д. Обычно вас не интересуют внутренние объекты этих объектов, только в представлении String. –

+0

Можете ли вы специально опубликовать свой код в общедоступном домене, пожалуйста? – stolsvik

+0

Если это всего лишь случай, когда я так говорю, я официально выпускаю вышеуказанный код в общественное достояние (без каких-либо подразумеваемых гарантий и всех подобных вещей). –

6

Я хотел элегантное решение этой проблемы, что:

  • не использует никаких внешних библиотек
  • Использует Reflection для доступа к полям, в том числе суперкласса полей
  • использует рекурсию для обхода объекта -граф с одним фреймом стека за звонок
  • Использует IdentityHashMap для обработки обратных ссылок и избежания бесконечной рекурсии
  • Ручка примитивов, авто-бокс, CharSequences, перечисления и аннулирует соответственно
  • Позволяет выбрать, будет ли или не разобрать статические поля
  • Достаточно просто изменить в соответствии с форматированием предпочтений

I написал следующий полезный класс:

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.

+0

хорошо, но мне пришлось наложить жесткий предел рекурсии 6500 дампа, поскольку вызывал stackoverflow –

+0

Начиная с написания этого сообщения, я впоследствии написал (не рекурсивную) итеративную версию, чтобы обойти ту же проблему! Я просмотрю свои файлы для кода и ссылки через Github, чтобы вы могли сравнить. –

+0

Хе-хе, я собирался спросить об этом, но у меня не было времени догадаться, возможно ли, круто спасибо :) –

4

Вы должны использовать RecursiveToStringStyle:

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

требует apache, но звучит интересно –

1

Вы можете использовать Gson представлять свой объект в формате JSON:

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

Я рекомендую вам использовать GSON Lib Fo Java.

Если вы используете Maven, вы можете использовать this.

Или вы можете скачать Jar-файл с here.

Вот пример того, как использовать его:

Gson gson = new GsonBuilder().setPrettyPrinting().create(); 
String json = gson.toJson(obj); 
System.out.println(json); 
Смежные вопросы