Reduce bugs by: not using toString()

The trouble with toString() in Java (or ToString() in C#) is that every object implements it. If you want to extract information from your object, define your own method, like getName(), as opposed to overriding toString().

In order to leverage the Java type-checker, you might want to define a class like FooName to wrap a String, as opposed to simply using a String. That way, a method that expects a name of a foo is both more self-documenting, and will now fail when pass the name of a bar.

To extract the underlying name of the foo, you might be tempted to override the toString() method, seemingly provided by the Java gods just for this purpose:

public class FooName {
   private final String name;
   public FooName(String name) { this.name = name; }
   public String toString() { return name; }
   @Override public int hashCode() { ... }
   @Override public boolean equals(Object other) { ... }
}

This forces any code using a FooName, and which needs the underlying name, to call toString() on the object. (The code might need the underlying name to e.g. query a database using a library which understandably doesn't know anything about the FooName class.)

If, one day, the code is changed from a FooName to a List<FooName>, ideally you'd want a compile error there. That code that queries the database for a single foo based on its name, cannot work when passed a list of foos! You want the type checker to tell you about code that cannot work.

But as long as you use toString(), the code will compile just fine! The toString() method is defined on all objects, including List<FooName>, even though it doesn't do anything useful.

The solution is to offer your own method such as getName(). If a variable gets changed to a List<FooName>, calls to getName() will immediately be highlighted by the IDE and you can correct the code. (e.g. changing the code to process the multiple objects as opposed to only one.)

The exception is for debugging. It's great to have the IDE, during debugging, show you what's in a variable by hovering over it. That can only work via toString(). To support this, without tempting actual users to use it, override toString() to show useful information for debugging, but not simply the actual name.

This is what the corrected class looks like:

public class FooName {
   private final String name;
   public FooName(String name) { this.name = name; }
   public String getName() { return name; }
   public String toString() { return getClass().getName()+"["+name+"]"; }
   @Override public int hashCode() { ... }
   @Override public boolean equals(Object other) { ... }
}

P.S. I recently created a nerdy privacy-respecting tool called When Will I Run Out Of Money? It's available for free if you want to check it out.

This article is © Adrian Smith.
It was originally published on 20 Feb 2017
More on: Java | Language Design | Coding