A constructor is the code that runs the moment an object is created, so it is the only place where a brand-new object is guaranteed to be in a usable state. Java also gives you field initializers and instance initializer blocks, and the order in which all three run is fixed, predictable and worth knowing by heart.
What happens when you write no constructor at all?
The compiler quietly supplies one for you: a no-argument constructor that leaves every field at its zero value. That is enough to compile, and it is exactly where a lot of first bugs live, because an object built that way can be in a state nobody planned for.
public class Demo {
static class Student {
String name;
int credits;
}
public static void main(String[] args) {
Student s = new Student();
System.out.println("name is null: " + (s.name == null));
System.out.println("credits: " + s.credits);
}
}The output confirms it: the name has no value at all, and the credits are zero. Nothing is broken here, but nothing is set up either. If a student is supposed to have a name and a course list, a constructor that insists on those values is what keeps the rest of the program from having to check for them over and over.
How do you write a constructor?
A constructor has the same name as the class, no return type, and a parameter list that becomes the object's initial state. The rule that trips people up is the missing return type: writing void Student(String name) turns your constructor into an ordinary method that the compiler refuses to let you call like a constructor.
public class Demo {
static class Student {
String name;
int credits;
final String id;
Student(String name, int credits) {
this.name = name;
this.credits = credits;
this.id = "STU-" + name.toUpperCase();
}
Student(String name) {
this(name, 0);
}
}
public static void main(String[] args) {
Student ada = new Student("Ada", 42);
Student bo = new Student("Bo");
System.out.println(ada.id + " has " + ada.credits + " credits");
System.out.println(bo.id + " has " + bo.credits + " credits");
}
}Two constructors, same class, different parameter lists. That is overloading, and it is what lets the second constructor delegate to the first: this(name, 0) calls the fuller constructor with a default of zero credits, so the id-building logic exists in exactly one place. If you ever change how an id is made, both constructors follow. The output shows STU-ADA has 42 credits and STU-BO has 0 credits.
Why does the this(...) call have to come first?
A constructor call has to be the very first statement in the body, because the compiler needs to know that the object is fully set up before anything else touches it. this(...) calls another constructor on the same object; super(...) calls the parent class constructor. You may use one or the other as the first statement, never both, and a constructor that starts with neither gets the no-argument parent constructor for free.
Chaining like this gives you a natural place to funnel validation. Every object of a class ends up passing through one constructor, so putting the checks there means no caller can bypass them.
import java.util.Objects;
public class Demo {
static class Student {
final String name;
Student(String name) {
this.name = Objects.requireNonNull(name, "name must not be null");
}
}
public static void main(String[] args) {
Student ok = new Student("Lin");
System.out.println("created " + ok.name);
try {
new Student(null);
} catch (NullPointerException e) {
System.out.println("rejected: " + e.getMessage());
}
}
}The program prints created Lin and then rejected: name must not be null. The object is never half-built: either the name is present and the object exists, or the constructor throws and no usable object comes out. That is the whole argument for validating in constructors rather than in a setter that somebody might forget to call.
In what order do initializers and constructors run?
There is a fixed sequence for every object, and it is worth memorising because it explains a surprising class of bug:
- Memory is allocated and the fields get their zero values.
- Field initializers run, in the order they are written in the class.
- Instance initializer blocks run, in the order they are written.
- The constructor body runs.
public class Demo {
static class Student {
String name;
int credits;
{
System.out.println("init block runs");
}
Student(String name) {
System.out.println("constructor body runs");
this.name = name;
}
}
public static void main(String[] args) {
new Student("Grace");
}
}The output shows the init block first and the constructor body second. The practical lesson is that instance initializer blocks are easy to misread, because they sit between the fields and the constructor with no name to hint at what they do. Most Java code you will read keeps them out, using field initializers or constructor calls instead.
Why mark a field final?
A final field can be assigned once and never pointed somewhere else afterwards. The compiler enforces it, which means it can trust the value for the rest of the object's life. For an identifier such as the student's id, that guarantee is exactly what you want: no method can quietly swap it out and leave every lookup pointing at the wrong record.
There is one wrinkle. A final field has to be definitely assigned by the time the constructor finishes, so every constructor path has to set it. When you have several constructors, the usual fix is to chain them all through one fuller constructor with this(...), exactly as the student example does. The alternative is a blank final that each constructor assigns itself, which works but duplicates the rule across constructors.
A constructor checklist to keep beside you
- Same name as the class, no return type, and it is called with
new. - Give the object a constructor for every way it should legitimately be created.
- Chain with
this(...)so the setup logic lives in one place. - Validate inside the constructor so the object is never born invalid.
- Field initializers and init blocks run before the constructor body, in written order.
- Mark identity fields
finalso they cannot change after construction.
Try it yourself: write a Booking class for a cinema with three fields: a row, a seat number and a boolean for whether the seat is taken. Give it one constructor that takes all three and one that takes only the row and seat, where the second chains to the first with the flag set to false. Add a field initializer that defaults the row to 0 and an instance initializer block that prints seat reserved when the taken flag is true. Create one fully specified booking and one short booking, print both, and confirm the init block only fires for the taken seat. Then change the short constructor to pass a row of -1 instead and watch the id-like field you add later inherit that value.