In the article about first constructor rule, we’ve seen that classes must have at least one constructor, otherwise they are reduced to a sorry imitation of what they should be. We also concluded that the first constructor in a class must validate the parameters and just assign them to the fields and do nothing else. Here we’ll cover the next rule: why such constructors deserve to be called primary constructors and why classes must have exactly one of them.

A class with a primary constructor

To quickly remind you, we ended up with a class which looked like this:

 1public final class Player {
 2    private final String name;
 3    private final int birthYear;
 4    private final double experience;
 5
 6    public Player(String name, int birthYear, double experience) {
 7        if (name == null) {
 8            throw new IllegalArgumentException("name cannot be null");
 9        }
10        if (birthYear < 1900) {
11            throw new IllegalArgumentException("birthYear must be >= 1900");
12        }
13        this.name = name;
14        this.birthYear = birthYear;
15        this.experience = experience;
16    }
17}

Two constructors

It’s easy to imagine a request which says “New Players must have 0 experience”. What I’ve often seen in such cases is that people write code like this:

 1public final class Player {
 2    //fields go here
 3
 4    public Player(String name, int birthYear) {
 5        this.name = name;
 6        this.birthYear = birthYear;
 7        this.experience = 0.0d;
 8    }
 9
10    //constructor with 3 parameters
11}

In this case, they’ve lost the validation. The constructor with 3 parameters validates them and prevents creation of invalid objects, but the constructor with 2 parameters does no validation at all. To solve this, developers often extract validation code and pat themselves on the back for removing duplication:

 1public final class Player {
 2    //fields go here
 3
 4    public Player(String name, int birthYear) {
 5        validate(name, birthYear);
 6        this.name = name;
 7        this.birthYear = birthYear;
 8        this.experience = 0.0d;
 9    }
10
11    public Player(String name, int birthYear, double experience) {
12        validate(name, birthYear);
13        this.name = name;
14        this.birthYear = birthYear;
15        this.experience = experience;
16    }
17
18    private static void validate(String name, int birthYear) {
19        if (name == null) {
20            throw new IllegalArgumentException("name cannot be null");
21        }
22        if (birthYear < 1900) {
23            throw new IllegalArgumentException("birthYear must be >= 1900");
24        }
25    }

No doubt this code looks better than the previous one because at least nobody can create an illegal object now. But I’d argue this isn’t the idiomatic object-oriented code. Our class now has a private static method which goes pretty much against object orientation. Static methods usually find themselves in some class by coincidence — a developer didn’t manage to find or didn’t care to find a better place for them. Such a method is difficult to test precisely because it’s private and static. While one might say this one is easy to test (at least indirectly through constructors) because it doesn’t call other methods, it’s a slippery slope and someone might easily make such methods actually call others. On a minor note, there is still some duplication because of the 2 calls to validate(name, birthYear);.

Two constructors - proper solution

In these situations I suggest making the constructor with the most parameters primary, with all other constructors calling it in the end. Concretely, the code would look like this:

 1public final class Player {
 2    private final String name;
 3    private final int birthYear;
 4    private final double experience;
 5
 6    public Player(String name, int birthYear) {
 7        this(name, birthYear, 0.0d);
 8    }
 9
10    public Player(String name, int birthYear, double experience) {
11        if (name == null) {
12            throw new IllegalArgumentException("name cannot be null");
13        }
14        if (birthYear < 1900) {
15            throw new IllegalArgumentException("birthYear must be >= 1900");
16        }
17        this.name = name;
18        this.birthYear = birthYear;
19        this.experience = experience;
20    }
21}

Just take a moment and look at how beautiful the code looks now!

  • There’s a primary constructor with 3 parameters which does validation
  • There’s a secondary constructor with 2 parameters which delegates to the primary one by using the constructor chaining pattern
  • There are no private static methods; everything is fully testable
  • Validation is centralized and works in all cases

Takeaways and action points

  • If a class has more than one constructor, one and only one must be the primary
  • Its main characteristics are validation and field assignment
  • All other constructors should delegate to it
  • Go through your code base, find classes which don’t follow this rule, and implement it properly! Your code and tests will thank you

Dear fellow developer, thank you for reading this article about a primary constructor rule. Until next time, TheJavaGuy salutes you 👋!