Constructors
A constructor runs when an object is created and sets it up in a valid state. Instead of creating an empty object and filling in fields one by one (and maybe forgetting one), you pass everything the object needs to new.
Writing a constructor
class Customer { String name; String email; Customer(String name, String email) { this.name = name; this.email = email; } } public class Main { public static void main(String[] args) { Customer c = new Customer("Ana Pereira", "ana@example.com"); System.out.println(c.name + " <" + c.email + ">"); } }
Ana Pereira <ana@example.com>
A constructor has the same name as the class and no return type. new Customer(…) allocates the object and then runs the constructor with the arguments.
The default constructor
If a class declares no constructor, Java provides an empty one with no parameters — that's why new Product() worked in lesson 23. As soon as you write any constructor, that free one disappears:
class Point { int x, y; Point(int x, int y) { this.x = x; this.y = y; } } public class Main { public static void main(String[] args) { Point p = new Point(); } }
Main.java:12: error: constructor Point in class Point cannot be applied to given types;
Overloaded constructors and this(…)
class Pizza { String size; int toppings; boolean extraCheese; Pizza(String size, int toppings, boolean extraCheese) { this.size = size; this.toppings = toppings; this.extraCheese = extraCheese; } Pizza(String size) { this(size, 0, false); } Pizza() { this("medium"); } String describe() { return size + ", " + toppings + " toppings" + (extraCheese ? ", extra cheese" : ""); } } public class Main { public static void main(String[] args) { System.out.println(new Pizza("large", 3, true).describe()); System.out.println(new Pizza("small").describe()); System.out.println(new Pizza().describe()); } }
large, 3 toppings, extra cheese small, 0 toppings medium, 0 toppings
A class can have several constructors with different parameters. this(…) calls another constructor of the same class, so the real work happens in one place. It must be the first statement.
Validating in the constructor
class Percentage { final int value; Percentage(int value) { if (value < 0 || value > 100) { throw new IllegalArgumentException("Percentage must be 0-100, got " + value); } this.value = value; } } public class Main { public static void main(String[] args) { System.out.println(new Percentage(40).value); new Percentage(140); } }
40
Exception in thread "main" java.lang.IllegalArgumentException: Percentage must be 0-100, got 140
at Percentage.<init>(Main.java:6)
at Main.main(Main.java:15)Because every object passes through a constructor, it's the ideal place to reject invalid data. Combined with final fields, an object can then never be in an invalid state. In stack traces, constructors appear as <init>.
final fields
class Order { final int id; final String customer; Order(int id, String customer) { this.id = id; this.customer = customer; } void rename(String newCustomer) { this.customer = newCustomer; } } public class Main { public static void main(String[] args) { } }
Main.java:11: error: cannot assign a value to final variable customer
A final field must be set exactly once, in the constructor (or its declaration), and can't change afterwards. Objects whose fields are all final are immutable: simpler to reason about and safe to share. Records (lesson 32) make such classes very short.
Initialisation order
class Demo { int a = 1; int b; { b = a + 1; System.out.println("initialiser block: a=" + a + ", b=" + b); } Demo() { System.out.println("constructor: a=" + a + ", b=" + b); } } public class Main { public static void main(String[] args) { new Demo(); } }
initialiser block: a=1, b=2 constructor: a=1, b=2
Field initialisers and initialiser blocks run in order, before the constructor body. In practice, keep setup in constructors so it's in one place.
Try it yourself
Write a BankAccount with an owner, an optional starting balance, and validation that the balance isn't negative.
Mei: 250.0 Lucas: 40.0