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.