Constructors
Java constructors: default, parameterised, this(), super(), validation. The shape that protects invariants.
Java — constructors
EXAMPLE
// ===== Default =====
public class User {
public User() {} // no-arg constructor (compiler generates if you write none)
}
// ===== Parameterised =====
public class User {
private final int id;
private final String name;
private String email;
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
}
// ===== Validation in constructor =====
public User(int id, String name, String email) {
if (id <= 0) throw new IllegalArgumentException("id must be positive");
if (name == null || name.isBlank()) throw new IllegalArgumentException("name required");
if (email == null || !email.contains("@")) throw new IllegalArgumentException("invalid email");
this.id = id;
this.name = name;
this.email = email;
}
// ===== Constructor chaining (this()) =====
public class User {
private final int id;
private final String name;
private final String email;
public User(int id, String name, String email) {
this.id = id; this.name = name; this.email = email;
}
public User(int id, String name) {
this(id, name, name + "@example.com"); // delegate to the main constructor
}
public User(int id) {
this(id, "unknown");
}
}
// this(...) MUST be the first statement.
// ===== Constructor chaining (super()) =====
public class Employee extends User {
private final long salaryCents;
public Employee(int id, String name, String email, long salaryCents) {
super(id, name, email); // call parent constructor
this.salaryCents = salaryCents;
}
}
// If you don't call super(...), Java inserts super() with no args.
// If the parent has no no-arg constructor, you MUST call super(...) explicitly.
// ===== Static factory methods (recommended for many cases) =====
public class Order {
private final UUID id;
private final String customer;
private Order(UUID id, String customer) {
this.id = id; this.customer = customer;
}
public static Order create(String customer) {
if (customer == null) throw new IllegalArgumentException("customer required");
return new Order(UUID.randomUUID(), customer);
}
public static Order fromRow(int id, String customer) {
return new Order(UUID.fromString(String.valueOf(id)), customer);
}
}
// Pros: named, can return cached instances, can return subtype.
// ===== Records: constructors come free =====
public record User2(int id, String name, String email) {
// Compact constructor for validation:
public User2 {
if (id <= 0) throw new IllegalArgumentException("id must be positive");
if (email == null || !email.contains("@")) throw new IllegalArgumentException("bad email");
}
}
// ===== Builder pattern (for many fields) =====
public class Request {
private final String url;
private final Map<String, String> headers;
private final Duration timeout;
private Request(Builder b) {
this.url = b.url; this.headers = b.headers; this.timeout = b.timeout;
}
public static class Builder {
private String url;
private Map<String, String> headers = Map.of();
private Duration timeout = Duration.ofSeconds(30);
public Builder url(String u) { this.url = u; return this; }
public Builder header(String k, String v) {
this.headers = new HashMap<>(this.headers); this.headers.put(k, v); return this;
}
public Builder timeout(Duration t) { this.timeout = t; return this; }
public Request build() {
Objects.requireNonNull(url, "url");
return new Request(this);
}
}
}
// Use:
Request r = new Request.Builder().url("...").timeout(Duration.ofSeconds(5)).build();
// ===== Patterns to internalise =====
// - final fields + validation in constructor = invariants from day 1
// - Static factory methods over public constructors when you need names / caching
// - records for value-shape classes with compact validation
// - Builder pattern for many optional fields
// ===== Pitfalls =====
// - Calling overridable methods from a constructor -> subclass sees uninitialised state
// - Leaking 'this' from constructor (e.g. registering with an EventBus) -> bad partial init
// - Forgetting super() when parent has no no-arg constructor
// - Throwing checked exceptions from a constructor — possible but awkward
Why it matters
Constructors guard invariants. Validate at construction, mark fields final, chain with this()/super(), reach for static factory methods or records when you need names or value shapes. The Builder pattern shines when there are many optional parameters; the goal is "you cannot make an invalid object".
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
public class Box {
int w, h;
public Box() { this(1, 1); } // no-arg
public Box(int w, int h) { this.w = w; this.h = h; }
}
Try it Yourself »
Discussion
Loading…