Java Constructors
›Short versionOpen if you only have a minute.
- A constructor has the class name and no return type. new runs it after the object is allocated.
- Its job is a valid object: required fields set, illegal arguments rejected, invariants true before the reference escapes.
- Overloads are different parameter lists. The compiler chooses one at the new expression. That is static dispatch.
- this(...) delegates to another constructor in the same class and must be the first statement.
- If you declare no constructor, the compiler supplies a no-argument one with the class’s access. Declare any constructor, and that default goes away.
- Constructors are not inherited. A subclass calls a superclass constructor with super(...), or the compiler inserts super().
A new account that already owes money is not an account you can hand to a teller. The moment of is the last quiet moment before other code can see the object. The is where you either finish a valid account or refuse to finish one.
The arguments that make it real
This constructor will not store a negative opening balance. Callers learn at new, which is earlier than a failed withdrawal months later.
public class Account {
private final String id;
private int balance;
public Account(String id, int opening) {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("id");
}
if (opening < 0) {
throw new IllegalArgumentException("opening");
}
this.id = id;
this.balance = opening;
}
}public void Account(String id, int opening) would compile as a method and would never run when you write new. The missing return type is the whole difference. There is nothing to return: the new expression already produces the reference.
Several ways in, one place that checks
An lets a caller omit a detail that has a known default. The compiler picks the constructor from the argument list. with keeps the checks in the fullest constructor.
public Account(String id) {
this(id, 0);
}
public Account(String id, int opening) {
if (id == null || id.isBlank() || opening < 0) {
throw new IllegalArgumentException();
}
this.id = id;
this.balance = opening;
}new Account("A-1") enters the first constructor, which immediately calls the second with 0. The first constructor's remaining body, if it had any, would run only after the second returned. this(...) has to be the first statement, so you cannot validate first and delegate second. Put the validation in the constructor that receives the real arguments.
Write no constructor at all, and the appears: no arguments, same access as the class, calling super(). Write Account(String, int) only, and new Account() stops compiling. That is usually what you want. An id-less account was never a meaningful object.
What the subclass must do
A subclass constructor does not reuse the superclass parameter list automatically. Its first act is super(...) or this(...). If you write neither, the compiler inserts super(), and compilation fails when the superclass has no no-argument constructor.
public class SavingsAccount extends Account {
public SavingsAccount(String id, int opening) {
super(id, opening);
}
}The order inside one new SavingsAccount(...) is fixed. Memory is reserved and fields sit at defaults. Object’s constructor finishes, then Account’s initializers and body, then SavingsAccount’s initializers, then the rest of the savings body. A savings field is still 0 or null while the account constructor is running.
That window is why the account constructor must not call a method a subclass can override, and must not store this in a list or hand it to another object. The override would run, or the other object would call back, before the subclass initializers had stored the real values. Assign fields, check arguments, and return. Register the account after new has handed the reference back.
The class declares only Account(String id, int opening). What does new Account() do, and where should a second constructor send the work?
new Account() does not compile. Declaring one constructor removed the default no-argument constructor. Add public Account(String id) only if an opening balance of zero is a real account, and make its first statement this(id, 0) so the checks stay in one constructor.