Constructors guarantee an object starts life in a valid state, which removes whole categories of bugs.
A constructor runs when an object is created with new. It has the class's name and no return type:
class Account {
private final String owner;
private long balanceCents;
Account(String owner, long openingCents) {
this.owner = owner;
this.balanceCents = openingCents;
}
}If you write no constructor, the compiler adds a no-argument default constructor. As soon as you write any constructor, that default disappears — a common surprise when a framework later needs a no-arg one.
You can overload constructors and have one delegate to another with this(...), which must be the first statement. Keep the real logic in the most complete constructor and let the others supply defaults:
Account(String owner) { this(owner, 0); }Validate arguments in the constructor and fail fast, so an invalid object can never exist. Objects.requireNonNull(owner, "owner") and an IllegalArgumentException for bad values are the standard tools.
final fields must be assigned exactly once, in a constructor or initializer. Making fields final wherever possible gives you objects that are easier to reason about and safe to share between threads.
When a class has many optional parameters, long constructor overload chains become unreadable. Options are a static factory method with a descriptive name (Account.empty(owner)), a builder, or — for plain data — a record, which generates the constructor for you.
Avoid calling overridable methods from a constructor: a subclass override would run before the subclass's own fields are initialised.
Example
import java.util.Objects;
public class Accounts {
static class Account {
private final String owner;
private long balanceCents;
Account(String owner, long openingCents) {
this.owner = Objects.requireNonNull(owner, "owner");
if (openingCents < 0) throw new IllegalArgumentException("negative opening balance");
this.balanceCents = openingCents;
}
Account(String owner) { this(owner, 0); } // delegate with a default
static Account withBonus(String owner) { return new Account(owner, 10_00); }
@Override public String toString() { return owner + ": " + balanceCents / 100.0; }
}
public static void main(String[] args) {
System.out.println(new Account("Ana")); // Ana: 0.0
System.out.println(Account.withBonus("Bo")); // Bo: 10.0
new Account("Cy", -5); // IllegalArgumentException
}
}Remember
- A constructor has the class name, no return type, and runs on
new - Writing any constructor removes the implicit no-arg one
- Chain with
this(...)and validate arguments so invalid objects never exist
Watch out for
- Calling an overridable method from a constructor — the subclass version runs before the subclass is initialised.