JAVA / CLASSES, CONSTRUCTORS AND ENCAPSULATION
Overloaded constructors and chaining with this()
Write several constructors for one class and have them delegate to a single canonical constructor with this(), so validation and assignment live in one place.
What you will learn
- Distinguish constructors by parameter types and count, never by parameter names
- Delegate with this(...) as the first statement so one constructor does the real work
- Funnel defaults inward: convenience constructors call the widest one
- Know that field initializers and super() run once, in the constructor ending the chain
Understanding Overloaded constructors and chaining with this()
A class may declare as many constructors as you like as long as their parameter lists differ in count, type, or order. Constructors have no return type and no name of their own, so the parameter list is the only thing the compiler can use to tell them apart: Point(int x, int y) and Point(int row, int col) are the same constructor declared twice. You want more than one because callers rarely hold the same information — some know every field, others know a name and expect sensible defaults for the rest.
The naive way to support several forms is to copy the checks and the this.field = assignments into each constructor, so every new field or rule has to be edited in several places. this(arg, ...) removes that duplication by running a sibling constructor of the same class on the object that is already being built; no second object is allocated, which is exactly why writing new Rectangle(...) inside a constructor is not a substitute. Java insists that this(...) be the very first statement because exactly one constructor in the chain runs the superclass constructor and the field initializers, and code placed before the delegation would be touching fields that do not exist yet. For the same reason the arguments you pass to this(...) may use parameters, literals and static methods, but may not read instance fields or call instance methods.
The mental model is a funnel. One canonical constructor takes the widest parameter list and is the only place that validates and assigns; every other constructor is a thin shim that supplies defaults and delegates inward with this(...). Statements written after the this(...) call run once the delegate has returned and the object is fully initialized, so a variant can still add a little extra work. Delegation must flow toward the canonical constructor and never form a cycle: two constructors that call each other are rejected at compile time as a recursive constructor invocation.
public class Main {
static class Rectangle {
private final int width;
private final int height;
private final String label;
// the canonical constructor: the only place that validates and assigns
Rectangle(int width, int height, String label) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("sides must be positive");
}
this.width = width;
this.height = height;
this.label = label;
System.out.println("canonical constructor ran for " + label);
}
Rectangle(int width, int height) {
this(width, height, "unnamed");
}
Rectangle(int side) {
this(side, side, "square " + side);
}
int area() {
return width * height;
}
@Override
public String toString() {
return label + " " + width + "x" + height + " area=" + area();
}
}
public static void main(String[] args) {
System.out.println(new Rectangle(3, 4, "banner"));
System.out.println(new Rectangle(3, 4));
System.out.println(new Rectangle(5));
try {
new Rectangle(0);
} catch (IllegalArgumentException e) {
System.out.println("rejected: " + e.getMessage());
}
}
}Overloaded constructors should be thin shims that funnel through this(...) into one canonical constructor which alone validates and assigns the fields.
Worked examples
What runs, and how many objects exist
Shows that the delegate finishes first, that field initializers run only once, and that this(...) does not allocate a second object.
import java.util.ArrayList;
import java.util.List;
public class Main {
static class Session {
private static int constructed = 0;
private final List<String> log = new ArrayList<>();
private final String user;
private final int timeoutSeconds;
Session(String user, int timeoutSeconds) {
this.user = user;
this.timeoutSeconds = timeoutSeconds;
log.add("built " + user);
constructed++;
}
Session(String user) {
this(user, 30);
log.add("used default timeout");
}
@Override
public String toString() {
return user + "/" + timeoutSeconds + " log=" + log;
}
}
public static void main(String[] args) {
System.out.println(new Session("ana", 5));
System.out.println(new Session("bo"));
System.out.println("sessions built: " + Session.constructed);
}
}Example explained
Line 1this(user, 30) runs the two-argument constructor to completion, so "built bo" is logged before the delegating body continues.
Line 2The log = new ArrayList<>() initializer executes only in the constructor that reaches super(), so delegating can never silently replace the list.
Line 3log.add("used default timeout") sits after this(...), which is legal and operates on a fully initialized object.
Line 4constructed ends at 2, not 3: only the two new expressions allocate objects, while this(...) re-enters a constructor body on the same object.
Overload choice is made by the compiler
Shows that two numeric constructors compile fine but route callers by declared argument type, not by intent.
public class Main {
static class Money {
private final long cents;
private final String note;
Money(long cents, String note) {
this.cents = cents;
this.note = note;
}
Money(long cents) {
this(cents, "raw cents");
}
Money(int dollars) {
this(dollars * 100L, "from dollars");
}
@Override
public String toString() {
return cents + " cents (" + note + ")";
}
}
public static void main(String[] args) {
System.out.println(new Money(7));
System.out.println(new Money(7L));
short seven = 7;
System.out.println(new Money(seven));
}
}Example explained
Line 1new Money(7) makes both Money(int) and Money(long) applicable, and the more specific int version wins, so 7 is read as dollars.
Line 2new Money(7L) cannot use Money(int) because long is never narrowed implicitly, so it lands on Money(long) and 7 means cents.
Line 3short seven widens to int as readily as to long, and the more specific int overload wins again, producing 700 cents from the same digit.
Line 4Both one-argument constructors still chain into Money(long, String), so the assignments themselves exist in exactly one place.
Preprocessing arguments with a static helper
Shows how to transform a value before delegating, given that no statement may precede this(...).
public class Main {
static class Tag {
private final String name;
private final int weight;
Tag(String name, int weight) {
if (name.isEmpty()) {
throw new IllegalArgumentException("empty name");
}
this.name = name;
this.weight = weight;
}
Tag(String rawName) {
this(normalize(rawName), 1);
}
private static String normalize(String raw) {
System.out.println("normalizing \"" + raw + "\"");
return raw.trim().toLowerCase();
}
@Override
public String toString() {
return name + ":" + weight;
}
}
public static void main(String[] args) {
System.out.println(new Tag(" Java "));
System.out.println(new Tag("java", 3));
}
}Example explained
Line 1this(normalize(rawName), 1) is still the first statement; argument expressions may contain calls, so the work does not need a line of its own.
Line 2normalize has to be static, because at that point the object is not initialized and an instance method call would not compile.
Line 3The normalizing line prints before any field is assigned, since arguments are evaluated before the delegate's body starts.
Line 4The isEmpty() guard exists only in the two-argument constructor, and the one-argument form inherits it by delegating.
Important notes
A constructor body may begin with this(...) or with super(...), never both; a delegating constructor reaches the superclass indirectly through the sibling it calls.
Arguments to this(...) are evaluated before the object is initialized, so they may use parameters, constants and static methods, but not instance fields or instance methods.
Common mistakes
Writing new Widget(name, 0) inside a constructor instead of this(name, 0): that builds and discards a second object while the one under construction keeps its 0/null defaults, or fails to compile if the fields are final and never assigned.
Putting a check or an assignment before this(...): the compiler reports that the call to this must be the first statement, and the fix is to move that logic into the canonical constructor or a static helper, not to abandon the delegation.
Trying to distinguish two constructors by parameter names, as in Point(int x, int y) and Point(int row, int col): the signatures are identical, so the second one is rejected as already defined.
Try it yourself
Change, predict, then run
Write a Player class whose three-argument constructor is the only one that rejects a level below 1 and assigns name, level and hitPoints, then add Player(String name) and Player(String name, int level) that delegate with this(...), defaulting level to 1 and hitPoints to level * 20. Print all three forms plus a caught failure for level 0, and check that the validation message appears exactly once in your source.
Open the Java workspaceCheck your understanding
Given class Cart { private List<String> items = new ArrayList<>(); Cart() { items.add("seed"); } Cart(String first) { this(); items.add(first); } }, what does the items list of new Cart("apple") hold?
- [seed, apple]
- [apple, seed]
- [apple], because this() re-runs the field initializer and replaces the list
- It does not compile, because a constructor that calls this() may not touch fields afterwards
Show answer
this() runs Cart() to completion first — the field initializer executes there and then "seed" is appended — and only when it returns does Cart(String) append "apple", giving [seed, apple]. The third option is wrong because field initializers run only in the constructor that invokes the superclass constructor; a constructor beginning with this(...) skips them, so the list is created once and never replaced.