Week 3 — What is constructor chaining?

Question of the Week #3
What is constructor chaining?
8 Replies
Eric McIntyre
Eric McIntyre2y ago
This means it's possible to have [omitting boilerplate for the sake of readability]
public Foo(A a, B b, C c) {
// logic
}

public Foo(A a, B b) {
this(a, b, defC());
}

public Foo(A a) {
this(a, defB());
}

public Foo(B b) {
this(defA(), b);
}

public Foo() {
this(defA());
}

public Foo(A a, C c) {
this(a, defB(), c);
}

public Foo(C c) {
this(defA(), c);
}
public Foo(A a, B b, C c) {
// logic
}

public Foo(A a, B b) {
this(a, b, defC());
}

public Foo(A a) {
this(a, defB());
}

public Foo(B b) {
this(defA(), b);
}

public Foo() {
this(defA());
}

public Foo(A a, C c) {
this(a, defB(), c);
}

public Foo(C c) {
this(defA(), c);
}
which can be represented as a tree
Foo(A, B, C)
/ \
Foo(A, B) Foo(A, C)
/ \ |
Foo(A) Foo(B) Foo(C)
|
Foo()
Foo(A, B, C)
/ \
Foo(A, B) Foo(A, C)
/ \ |
Foo(A) Foo(B) Foo(C)
|
Foo()
Constructor chaining refers to creating overloaded constructors of a class, specifically those with "default" parameters, by chaining the constructors linearly. This allows the caller to omit parameters that are either not important or can assume their default value.
class Foo {

// references and attributes
private String str;
private int value;
private LocalDate date;

// 'original' constructor, contains logic
public Foo(String str, int value, LocalDate date) {
this.str = str;
this.value = value;
this.date = date;
}

// first overloaded constructor, defining a 'default' date
public Foo(String str, int value) {
this(str, value, LocalDate.now());
}

// second overloaded constructor, calling the first
// adding a default int and String
public Foo() {
this("", 0);
}

}
class Foo {

// references and attributes
private String str;
private int value;
private LocalDate date;

// 'original' constructor, contains logic
public Foo(String str, int value, LocalDate date) {
this.str = str;
this.value = value;
this.date = date;
}

// first overloaded constructor, defining a 'default' date
public Foo(String str, int value) {
this(str, value, LocalDate.now());
}

// second overloaded constructor, calling the first
// adding a default int and String
public Foo() {
this("", 0);
}

}
A real world example from the java standard library includes the BufferedWriter class
private BufferedWriter(Writer out, int initialSize, int maxSize) {
// logic
}

public BufferedWriter(Writer out) {
this(out, initialBufferSize(), DEFAULT_MAX_BUFFER_SIZE);
}

public BufferedWriter(Writer out, int sz) {
this(out, sz, sz);
}
private BufferedWriter(Writer out, int initialSize, int maxSize) {
// logic
}

public BufferedWriter(Writer out) {
this(out, initialBufferSize(), DEFAULT_MAX_BUFFER_SIZE);
}

public BufferedWriter(Writer out, int sz) {
this(out, sz, sz);
}
or WeakHashMap
public WeakHashMap(int initialCapacity, float loadFactor) {
// logic
}

public WeakHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public WeakHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

public WeakHashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) Math.ceil(m.size() / (double)DEFAULT_LOAD_FACTOR),
DEFAULT_INITIAL_CAPACITY),
DEFAULT_LOAD_FACTOR);
putAll(m);
}
public WeakHashMap(int initialCapacity, float loadFactor) {
// logic
}

public WeakHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public WeakHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

public WeakHashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) Math.ceil(m.size() / (double)DEFAULT_LOAD_FACTOR),
DEFAULT_INITIAL_CAPACITY),
DEFAULT_LOAD_FACTOR);
putAll(m);
}
which, ironically unidiomatically, does not use this(DEFAULT_INITIAL_CAPACITY) but this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR) which allows us to see that even the standard library is not infallible While this chaining is linear from any given constructor itself the overall structure is tree-like
Eric McIntyre
Eric McIntyre2y ago
It should be noted though, that performance wise this could have proper reasoning, as each chained call adds a stackframe which can be avoided if you duplicate a tiny bit of code. While this would not make much of a difference it is possible that this choice was deliberate.
⭐ Submission from jade#9418
Eric McIntyre
Eric McIntyre2y ago
As seen here, each constructor calls one with more parameters than itself, until it eventually calls the 'original' constructor which implements the logic. It is important to notice that each constructor mainly calls another one, and does not implement extra logic. While this is possible it should be avoided unless the computation is pure in itself and is then added as an argument for the call to the overloaded constructor or to a class field.
Eric McIntyre
Eric McIntyre2y ago
Constructor chaining is the process of calling a constructor with an object argument that is created using its constructor in the same statement. If there are two types like the following:
public record A(int i){}
public record B(A a){}
public record A(int i){}
public record B(A a){}
then it is possible to create an instance of A and pass that directly to the instance of B as follows:
B b=new B(new A(1337));
B b=new B(new A(1337));
As this calls the constructor of A and passes the created object to B, it is called constructor chaining.
⭐ Submission from dan1st#7327
Eric McIntyre
Eric McIntyre2y ago
Constructor chaining is the process of calling a sequence of constructors. We can do it in two ways: by using this() keyword for chaining constructors in the same class by using super() keyword for chaining constructors from the parent class
Submission from sahilasopa#2787
Eric McIntyre
Eric McIntyre2y ago
In Java, a constructor is a method that is used to initialise an object. Constructor chaining refers to the process of calling one constructor from another constructor, either within the same class or from a subclass. There are two ways to chain constructors in Java: Within the same class: You can call one constructor from another constructor within the same class by using the this keyword. This is useful when you have multiple constructors in a class and you want to share some common initialisation code between them. For example:
public class Employee {
private String name;
private int age;
private String department;

// Default constructor
public Employee() {
this("John", 30, "IT");
}

// Constructor with name and age
public Employee(String name, int age) {
this(name, age, "IT");
}

// Constructor with all parameters
public Employee(String name, int age, String department) {
this.name = name;
this.age = age;
this.department = department;
}
}
public class Employee {
private String name;
private int age;
private String department;

// Default constructor
public Employee() {
this("John", 30, "IT");
}

// Constructor with name and age
public Employee(String name, int age) {
this(name, age, "IT");
}

// Constructor with all parameters
public Employee(String name, int age, String department) {
this.name = name;
this.age = age;
this.department = department;
}
}
In the above example, the Employee class has three constructors: a default constructor, a constructor that takes a name and age, and a constructor that takes all three parameters (name, age, and department). The default constructor and the constructor with name and age both call the constructor with all three parameters using the this keyword. This allows them to share the common initialization code for setting the name, age, and department fields.
Eric McIntyre
Eric McIntyre2y ago
From a subclass: You can also call a superclass constructor from a subclass constructor using the super keyword. This is useful when you want to inherit the properties and behavior of a superclass, but also add some additional initialization code in the subclass. For example:
public class Person {
private String name;
private int age;

// Constructor with name and age
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

public class Employee extends Person {
private String department;

// Constructor with name, age, and department
public Employee(String name, int age, String department) {
super(name, age); // Call the superclass constructor
this.department = department;
}
}
public class Person {
private String name;
private int age;

// Constructor with name and age
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

public class Employee extends Person {
private String department;

// Constructor with name, age, and department
public Employee(String name, int age, String department) {
super(name, age); // Call the superclass constructor
this.department = department;
}
}
In the above example, the Employee class extends the Person class and adds a department field. The Employee class has a constructor that takes a name, age, and department as parameters. This constructor calls the Person class's constructor using the super keyword, passing in the name and age parameters. This allows the Employee object to inherit the name and age fields from the Person class, and also initialise the department field.
Submission from Liquid#2980
Eric McIntyre
Eric McIntyre2y ago
Constructor chaining is the act of calling constructors in the same class (this()) or parent class (super()) during object initialization, from another constructor. A constructor should call super() first to ensure the inherited members are initialized.
class Cat extends Pet {
public Cat() { this("Nova"); }
public Cat(String name) {
super(name);
System.out.println("Created Cat named " + this.name);
}
}

class Pet {
public String name;
public Pet(String name) { this.name = name; }
}
class Cat extends Pet {
public Cat() { this("Nova"); }
public Cat(String name) {
super(name);
System.out.println("Created Cat named " + this.name);
}
}

class Pet {
public String name;
public Pet(String name) { this.name = name; }
}
Submission from Fright XO#1337
Want results from more Discord servers?
Add your server