1

This is (maybe) a pretty basic question, but I'd like to know if there are some consolidated best practices for overriding toString method for complex object.

Let's say I have a class defined like this:

public class MyClass {
    private int id;
    private ClassWithLotsOfFields field1;
    private AnotherClassWithLotsOfFields field2;
    ...
    private List<ComplexObject> listOfComplexObjects;
    
    // accessors ...
}

and suppose ClassWithLotsOfFields, AnotherClassWithLotsOfFields and ComplexObject are declaring fields of "complex" types.

In these cases should I call toString for all these nested classes, creating a "gigantic" toString output or should I simplify the output string in any way?

--- Update

For example: is it "acceptable" to print only certain fields of the "nested" objects? Or completely exclude them from the string output?

davioooh
  • 23,742
  • 39
  • 159
  • 250
  • 1
    you want us to decide what the behavior of your code should be, without knowing your requirements? – Stultuske Sep 14 '21 at 07:59
  • Does this answer your question? [How to use the toString method in Java?](https://stackoverflow.com/questions/3615721/how-to-use-the-tostring-method-in-java). As per the spec, you're supposed to return a "textual representation". That seems pretty open to whatever your interpretation/needs might be. – Janus Varmarken Sep 14 '21 at 08:00
  • @Stultuske, I'm just asking if there is a general "rule". – davioooh Sep 14 '21 at 08:01
  • 1
    `toString()` *"should be a concise but informative representation that is easy for a person to read"*. That is very subjective indeed. There is no general rule of how to weigh these things. – MC Emperor Sep 14 '21 at 08:06
  • @davioooh my general rule is do not touch it unless you need to. Unless you have a specific reason to configure it, dont touch it. If you do we do not tell you what it must look like, you already know. – Dimitris Sep 14 '21 at 08:07
  • 1
    I would personally include everying (except fields representing internal state), but I would also refrain from complex objects like those you are describing. – MC Emperor Sep 14 '21 at 08:09
  • @MCEmperor ok, clear! – davioooh Sep 14 '21 at 08:09

3 Answers3

2

From the documentation of Object::toString:

Returns a string representation of the object. In general, the toString method returns a string that "textually represents" this object. The result should be a concise but informative representation that is easy for a person to read.

Emphasis mine.

That is very subjective indeed. There is no general rule of how to weigh these things. Many toString() implementations return the class name and then the toString() result of all fields. A particular implementation may look like this:

class SomeClass {
    private Alpha alpha;
    private Bravo bravo;

    @Override
    public String toString() {
        return String.format("SomeClass(alpha=%s, bravo=%s)", alpha, bravo);
    }
}

There even exist tools to autogenerate the toString() implementation, like Lombok.

However, the abovementioned example is mainly used for simpler classes. I have never seen complex objects like you are describing. If I had such a complex object, I would personally include everying, except for fields representing internal state.

But what's more, I would also refrain from complex objects like you are describing, unless I absolutely have to.

MC Emperor
  • 22,334
  • 15
  • 80
  • 130
  • "But what's more, I would also refrain from complex objects like you are describing, unless I absolutely have to." Yes, probably is a *code smell*! Thanks. – davioooh Sep 14 '21 at 08:23
1

As comments have shown, there is no strict specific way to decide the contents of your toString method. But to help you decide, keep in mind the three general scenarios where you examine an object’s details:

  • Minute detail, looking at any of the various values within an object. For this you are likely deep in a debugging session in which case you are using the powerful debugger tooling found in today’s IDEs.
  • Summary presentation to the user. This should generally be designed for localization requiring a Locale. For this, notice how some classes offer a getDisplayName method. For example, java.time.DayOfWeek.
  • Identifying an object briefly, for messages in logging, and for quick dumps to the console for testing or for sanity-checks while developing. This is the main use for toString in my experience.

So I suggest including only enough detail in your toString method as needed to identify the object with certainty. For example, UUID or sequential ID fields. And fields like “Student ID”, “Employee ID”, “ Invoice Number”, and such.

Perhaps include a few extra fields to get a sense of the data. Those few extra fields might be the ones you are likely to search and sort on.

Omit more trivial fields which are likely to be of little interest during most of your development, testing, and logging.

Most importantly, consider the sensitivity of your data. The toString method tends to be called from many places, quite often, and quite casually. While many of those calls are ephemeral, calls to toString for logging in particular may result in data being written in plain text to files that may be quite long-lived. So I would recommend omitting the fields that may be sensitive, private, or have security implications.

Note that in Java 16+, a class defined as a record automatically gets a default toString implementation provided implicitly by the compiler. That default implementation includes all the fields you specified for your record. For a record with many fields, or with sensitive fields, you may choose to provide your own custom implementation of toString.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
1

The IDEs generate a starting version. Drop irrelevant fields (maxWeight, color), some fields refencing other objects might use their ID field. Some object fields might be important, say Rectangle for calculation, then their toString.

Short one-liners are best, like just the ID.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138