Reflective toString()

Tired of writing toString() on all your classes? Annoyed when your co-workers forget to do so? Here’s a simple solution that uses reflection to implement toString().

Find two source files. ToString.java is a helper class for implementing toString(). It’s a revision of the Google Guava’s ToStringHelper.

public class ToString {
	private final List<ValueHolder> valueHolders = new LinkedList<ValueHolder>();
	private boolean omitNullValues = false;

	protected final Object obj;

	public ToString(Object obj) {
		this.obj = obj;
	}

	public ToString add(String name, Object value) {
		addHolder(value).builder.append(name).append('=').append(value);
		return this;
	}

	public ToString add(String name, boolean value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, char value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, double value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, float value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, int value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, long value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	private StringBuilder checkNameAndAppend(String name) {
		return addHolder().builder.append(name).append('=');
	}

	@Override
	public String toString() {
		// create a copy to keep it consistent in case value changes
		boolean omitNullValuesSnapshot = omitNullValues;
		boolean needsSeparator = false;
		StringBuilder builder = new StringBuilder(32).append(
				obj.getClass().getSimpleName()).append('{');
		for (ValueHolder valueHolder : valueHolders) {
			if (!omitNullValuesSnapshot || !valueHolder.isNull) {
				if (needsSeparator) {
					builder.append(", ");
				} else {
					needsSeparator = true;
				}
				CharSequence sequence = valueHolder.builder;
				builder.append(sequence);
			}
		}
		return builder.append('}').toString();
	}

	private ValueHolder addHolder() {
		ValueHolder valueHolder = new ValueHolder();
		valueHolders.add(valueHolder);
		return valueHolder;
	}

	private ValueHolder addHolder(Object value) {
		ValueHolder valueHolder = addHolder();
		valueHolder.isNull = (value == null);
		return valueHolder;
	}

	private static final class ValueHolder {
		final StringBuilder builder = new StringBuilder();
		boolean isNull;
	}
}

ReflectiveToString.java extends the former, and re-implements toString() to automatically add all (most) fields.

public class ReflectiveToString extends ToString {
	private static final Pattern[] IGNORE_FIELD_PATTERNS = new Pattern[] {
			Pattern.compile("^this\\$\\d+$"),
			Pattern.compile("^serialVersionUID$") };

	public ReflectiveToString(Object obj) {
		super(obj);
	}

	@Override
	public String toString() {
		Set<Field> fields = getFields();
		fields: for (Field f : fields) {
			String fn = f.getName();
			for (Pattern p : IGNORE_FIELD_PATTERNS) {
				Matcher m = p.matcher(fn);
				if (m.matches()) {
					continue fields;
				}
			}

			f.setAccessible(true);
			try {
				add(fn, f.get(obj));
			} catch (IllegalArgumentException e) {
				// silently skip, should never happen
			} catch (IllegalAccessException e) {
				// silently skip, should never happen
			}
		}

		return super.toString();
	}

	private Set<Field> getFields() {
		Set<Field> fields = new HashSet<Field>();
		getFields(obj.getClass(), fields);

		return fields;
	}

	private static void getFields(Class<? extends Object> cls, Set<Field> fields) {
		Class<? extends Object> superclass = cls.getSuperclass();
		if (superclass != null) {
			getFields(superclass, fields);
		}
		fields.addAll(Arrays.asList(cls.getDeclaredFields()));
	}
}

To use it, implement toString() on your classes like this,

@Override
public String toString() {
    return new ReflectiveToString(this).toString();
}

You could of course extend Guava’s ToStringHelper directly. I didn’t want to pull in the entire library however.

Of course the string output can be tuned by modifying ToString.java.

One Response to Reflective toString()

  1. Jeffrey Blattman says:

    As a side note, I was looking into using reflection to implement hashCode() and equals(). The reason why not is performance. Those methods are called A LOT, and a reflective version of equals was measured at around 100x slower than the non-reflective version. By the same token, the above toString() code should be used with caution.

Leave a comment