Programing

자바 객체의 속성 덤프

lottogame 2020. 11. 24. 07:34
반응형

자바 객체의 속성 덤프


객체 속성을 재귀 적으로 덤프 / 인쇄하는 라이브러리가 있습니까? 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 () 메서드를 재정의 할 수 있었지만 실제로는 수정할 수없는 외부 개체를 처리하고 있습니다.


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을 사용해 보았지만 덤프하려는 객체 그래프에 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;
    }
}

사용자 지정 ToStringStyle과 함께 ReflectionToStringBuilder를 사용할 수 있습니다. 예를 들면 다음과 같습니다.

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 게시물 수정 버전

참고 :이 방법은 재귀를 사용하므로 매우 깊은 개체 그래프가있는 경우 스택 오버플로 (말장난 없음;) IF가 발생할 수 있으므로 VM 매개 변수 -Xss10m을 사용해야합니다. Eclipse를 사용하는 경우 run> runconfiguration> augments (tab) VM augment box에 넣고 적용을 누릅니다.

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

이 문제에 대한 우아한 해결책을 원했습니다.

  • 외부 라이브러리를 사용하지 않습니다.
  • Reflection사용 하여 수퍼 클래스 필드를 포함한 필드에 액세스
  • 재귀를 사용하여 호출 당 하나의 스택 프레임으로 오브젝트 그래프를 횡단합니다.
  • IdentityHashMap사용하여 역방향 참조를 처리하고 무한 재귀를 방지합니다.
  • 프리미티브, 자동 박싱, CharSequences, enum 및 null을 적절하게 처리합니다.
  • 정적 필드를 구문 분석할지 여부를 선택할 수 있습니다.
  • 서식 기본 설정에 따라 수정할 수있을만큼 간단합니다.

다음 유틸리티 클래스를 작성했습니다.

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

편집하다

이 게시물을 작성한 이후로이 알고리즘의 반복 버전을 만들 이유가있었습니다. 재귀 버전은 전체 스택 프레임에 의해 깊이가 제한되지만 매우 큰 개체 그래프를 덤프 할 이유가있을 수 있습니다. 내 상황을 처리하기 위해 런타임 스택 대신 스택 데이터 구조를 사용하도록 알고리즘을 수정했습니다. 이 버전은 시간 효율적이며 스택 프레임 깊이 대신 힙 크기로 제한됩니다.

여기 에서 반복 버전을 다운로드하여 사용할 수 있습니다 .


RecursiveToStringStyle을 사용해야합니다.

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

Maybe you could use an XML binding framework like XStream, Digester or JAXB for that.


You could use Gson to represent your object in json format :

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

JSONObject.fromObject(value)

Does not work for Map objects with other keys than String. Maybe JsonConfig can handle this.


I recommend you to use the GSON Lib fo Java.

if You use Maven you can use this.

Or you can download the Jar file from here.

Here example how to use it:

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

참고URL : https://stackoverflow.com/questions/603013/dumping-a-java-objects-properties

반응형