One important feature that can be implemented using constructors but can NOT be implemented with "normal methods" is to provide invariants:
It's very useful if a class can guarantee certain properties of all its instances (sometimes these are called "invariants").
For example, you could have a Person
class that guarantees that givenName
and lastName
will always have valid, non-null, non-empty values (which is absolutely not true in the real world, by the way, but let's pretend that the world is simple for a second).
You could do that with a constructor that takes the parameters and verifies that they are valid:
public class Person {
private final String givenName;
private final String lastName;
public Person(String givenName, String lastName) {
this.givenName = verifyValidName(givenName);
this.lastName = verifyValidName(lastName);
}
private static String verifyValidName(String name) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("name is null or empty");
}
return name;
}
// getters
}
Note that in a real, current codebase this should almost certainly be a record
instead of a regular class, but that doesn't fundamentally influence the answer and new developers might not yet know record
classes.
With this code any code that uses a Person
object (independent of whether it created it on its own or it got passed from somewhere else) knows that the two fields will be non-null. There's no need to pepper person.hasValidNames()
calls all throughout your code, because you know that no Person
can exist where givenName
is null
.
If you instead initialized the fields in a normal method, then the default constructor would set the fields to null
. That's not the end of the world itself, but it means that code that gets passed from anywhere else needs to consider the option that either one of those fields could be null
, which potentially adds a lot of complexity.