Java toString - ToStringBuilder недостаточно; не пройдет
Мне нужно пройти через весь мой граф объектов и записать все содержимое всех полей.
Например: Object A имеет коллекцию Object B, которая имеет набор объектов C и A, B, C, имеет дополнительные поля и т.д.
Apache Commons ToStringBuilder недостаточно, так как он не пересечет граф объекта или содержимое вывода коллекции.
Кто-нибудь знает о другой библиотеке, которая сделает это или имеет фрагмент кода, который делает это?
Ответы
Ответ 1
Вы можете перемещаться по всему дереву с помощью org.apache.commons.lang.builder.ReflectionToStringBuilder
. Фокус в том, что в ToStringStyle
вам нужно пройти в значение. ToStringStyle
позаботится о уже обработанных значениях и не позволит рекурсии. Здесь мы идем:
System.out.println(ReflectionToStringBuilder.toString(schema, new RecursiveToStringStyle(5)));
private static class RecursiveToStringStyle extends ToStringStyle {
private static final int INFINITE_DEPTH = -1;
/**
* Setting {@link #maxDepth} to 0 will have the same effect as using original {@link #ToStringStyle}: it will
* print all 1st level values without traversing into them. Setting to 1 will traverse up to 2nd level and so
* on.
*/
private int maxDepth;
private int depth;
public RecursiveToStringStyle() {
this(INFINITE_DEPTH);
}
public RecursiveToStringStyle(int maxDepth) {
setUseShortClassName(true);
setUseIdentityHashCode(false);
this.maxDepth = maxDepth;
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
}
else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
// another helpful method
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Collection<?> coll) {
depth++;
buffer.append(ReflectionToStringBuilder.toString(coll.toArray(), this, true, true));
depth--;
}
}
Ответ 2
Ниже приведена модифицированная версия решения @dma_k, включающая повторное использование одного буфера, безопасность потоков, многострочный отступ и использование метода toString
объекта, если оно было переопределено.
Пример вывода:
ToStringTest.ParentStub {
array = {a,b,c}
map = {key2=null, key1=value1}
child = ToStringTest.Stub {
field1 = 12345
field2 = Hello
superField = abc
}
empty = <null>
superField = abc
}
код:
class RecursiveToStringStyle extends ToStringStyle {
private static final RecursiveToStringStyle INSTANCE = new RecursiveToStringStyle(13);
public static ToStringStyle getInstance() {
return INSTANCE;
}
public static String toString(Object value) {
final StringBuffer sb = new StringBuffer(512);
INSTANCE.appendDetail(sb, null, value);
return sb.toString();
}
private final int maxDepth;
private final String tabs;
// http://stackoverflow.com/a/16934373/603516
private ThreadLocal<MutableInteger> depth = new ThreadLocal<MutableInteger>() {
@Override
protected MutableInteger initialValue() {
return new MutableInteger(0);
}
};
protected RecursiveToStringStyle(int maxDepth) {
this.maxDepth = maxDepth;
tabs = StringUtils.repeat("\t", maxDepth);
setUseShortClassName(true);
setUseIdentityHashCode(false);
setContentStart(" {");
setFieldSeparator(SystemUtils.LINE_SEPARATOR);
setFieldSeparatorAtStart(true);
setFieldNameValueSeparator(" = ");
setContentEnd("}");
}
private int getDepth() {
return depth.get().get();
}
private void padDepth(StringBuffer buffer) {
buffer.append(tabs, 0, getDepth());
}
private StringBuffer appendTabified(StringBuffer buffer, String value) {
//return buffer.append(String.valueOf(value).replace("\n", "\n" + tabs.substring(0, getDepth())));
Matcher matcher = Pattern.compile("\n").matcher(value);
String replacement = "\n" + tabs.substring(0, getDepth());
while (matcher.find()) {
matcher.appendReplacement(buffer, replacement);
}
matcher.appendTail(buffer);
return buffer;
}
@Override
protected void appendFieldSeparator(StringBuffer buffer) {
buffer.append(getFieldSeparator());
padDepth(buffer);
}
@Override
public void appendStart(StringBuffer buffer, Object object) {
depth.get().increment();
super.appendStart(buffer, object);
}
@Override
public void appendEnd(StringBuffer buffer, Object object) {
super.appendEnd(buffer, object);
buffer.setLength(buffer.length() - getContentEnd().length());
buffer.append(SystemUtils.LINE_SEPARATOR);
depth.get().decrement();
padDepth(buffer);
appendContentEnd(buffer);
}
@Override
protected void removeLastFieldSeparator(StringBuffer buffer) {
int len = buffer.length();
int sepLen = getFieldSeparator().length() + getDepth();
if (len > 0 && sepLen > 0 && len >= sepLen) {
buffer.setLength(len - sepLen);
}
}
private boolean noReflectionNeeded(Object value) {
try {
return value != null &&
(value.getClass().getName().startsWith("java.lang.")
|| value.getClass().getMethod("toString").getDeclaringClass() != Object.class);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(e);
}
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
if (getDepth() >= maxDepth || noReflectionNeeded(value)) {
appendTabified(buffer, String.valueOf(value));
} else {
new ReflectionToStringBuilder(value, this, buffer, null, false, false).toString();
}
}
// another helpful method, for collections:
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Collection<?> coll) {
buffer.append(ReflectionToStringBuilder.toString(coll.toArray(), this, true, true));
}
static class MutableInteger {
private int value;
MutableInteger(int value) { this.value = value; }
public final int get() { return value; }
public final void increment() { ++value; }
public final void decrement() { --value; }
}
}
Ответ 3
Я не знаю библиотеки наизусть, но это довольно легко с отражением api и некоторой рекурсией:
printMembers(Object instance)
foreach field
if (field is primitive or String) // guess you're interested in the String value
printPrimitive(field)
else if (field is array or collection)
foreach item in field
printmembers(item)
else
printmembers(field) // no primitve, no array, no collection -> object
Получение всех полей не является проблемой с API Java Reflection. Если поле является массивом или экземпляром Iterable
, просто используйте итератор, чтобы получить все обработчики массива/коллекции.
С помощью специальной реализации вы можете добавлять специальные обработчики для специальных объектов (например, обрабатывать String как примитив), чтобы избежать беспорядка в журналах.
Ответ 4
Это то, что я написал для личного использования. Дайте мне знать, если это поможет:
public static String arrayToString(final Object obj){
if (obj == null) {
return "<null>";
}
else {
Object array = null;
if (obj instanceof Collection) {
array = ((Collection) obj).toArray();
}
else if (obj.getClass().isArray()) {
array = obj;
}
else {
return notNull(obj);
}
int length = Array.getLength(array);
int lastItem = length - 1;
StringBuffer sb = new StringBuffer("[");
for (int i = 0; i < length; i++) {
sb.append(arrayToString(Array.get(array, i)));
if (i < lastItem) {
sb.append(", ");
}
}
sb.append(']');
return sb.toString();
}
}
Ответ 5
Эта ссылка оказалась хорошей отправной точкой. Вам в основном нужно что-то рекурсивное, но не потеряется в циклических ссылках (Object A имеет ссылку на Object B, который ссылается на Object A, вы не хотите снова и снова перебирать его).
http://www.java2s.com/Code/Java/Class/Constructsprettystringrepresentationofobjectvalue.htm
Это также было несколько полезно
http://binkley.blogspot.com/2007/08/recursive-tostring.html
Ответ 6
Расширенный код для списка и карты:
Код:
public class MultipleRecursiveToStringStyle extends ToStringStyle {
private static final int INFINITE_DEPTH = -1;
private int maxDepth;
private int depth;
public MultipleRecursiveToStringStyle() {
this(INFINITE_DEPTH);
}
public MultipleRecursiveToStringStyle(int maxDepth) {
setUseShortClassName(true);
setUseIdentityHashCode(false);
this.maxDepth = maxDepth;
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Collection<?> coll) {
Collections.sort((List<Comparable>) coll);
for(Object value: coll){
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Map<?, ?> map) {
TreeMap<?,?> sortedMap = new TreeMap<Object, Object>(map);
for(Map.Entry<?,?> kvEntry: sortedMap.entrySet()){
Object value = kvEntry.getKey();
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
value = kvEntry.getValue();
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
}}