A constructor is the block that initializes a new instance of a class.
In the previous article we created our first Robot objects, but there was something that “smelled bad” in the code.
We had to do this:
Robot r1 = new Robot(); // Born empty (null name, 0 battery)
// <--- Here the robot exists but is invalid/useless
r1.nombre = "Wall-E"; // We fix it manually
r1.nivelBateria = 100;This is dangerous. What if a distracted programmer forgets to set those configuration lines? We would have a nameless Robot wandering through our system, and when it tries to speak, the program will crash.
To solve this, Java uses Constructors. A mechanism to force the object to be born already configured and ready to use.
What is a constructor?
A constructor is a special block of code that executes automatically the moment you do new.
Its mission is to initialize the object. That is, to give initial values to the attributes to ensure the object has a valid state from millisecond zero.
Syntax
The constructor has two golden rules:
- It is named exactly the same as the class.
- It has no return type (not even
void).
public class Robot {
String nombre;
int nivelBateria;
// THIS IS THE CONSTRUCTOR
public Robot(String initialName, int initialBattery) {
nombre = initialName;
nivelBateria = initialBattery;
}
}Now, when creating the object, Java forces us to pass the data. We can no longer create empty robots by mistake.
// Robot r = new Robot(); // COMPILATION ERROR! Java requires parameters.
Robot r1 = new Robot("Wall-E", 100); // Born perfect.The this keyword
In the previous example I used dirty tricks like naming the parameter initialName so it wouldn’t match the attribute name.
In real life, we want the parameters to have the same names as the attributes (nombre, bateria). But if we do this, we have an ambiguity problem (Shadowing):
public Robot(String nombre) {
nombre = nombre; // Who is who?
// Here Java assumes both are the local variable (the parameter).
// The class attribute is NOT modified.
}To break this ambiguity we use this.
this is a reference that points to the instance itself (the object executing the code).
Read it as: “In my attribute name (this.nombre), store the value of the parameter nombre”.
public Robot(String nombre, int nivelBateria) {
this.nombre = nombre; // Correct
this.nivelBateria = nivelBateria; // Correct
}The default constructor (the invisible gift)
If you do not write any constructor in your class, Java is kind and inserts an invisible one for you: the Default Constructor (no parameters and empty).
// Java inserts this if you put nothing:
public Robot() { }That’s why in the previous article we could do new Robot().
The moment you write any constructor (even one with parameters), Java says: “Ah, you already know what you’re doing, I withdraw my help”. The default constructor disappears.
If you still want to be able to create empty objects (new Robot()), you will have to explicitly write the empty constructor yourself.
Constructor overloading
Sometimes we want flexibility. Perhaps we want to create a Robot by providing all the data, or perhaps we want to create a Robot by providing only the name and assuming the battery starts at 100%.
Java allows having multiple constructors in the same class, as long as they have different parameter lists. This is called Overloading.
public class Robot {
String nombre;
int nivelBateria;
// Option A: Providing everything
public Robot(String nombre, int nivelBateria) {
this.nombre = nombre;
this.nivelBateria = nivelBateria;
}
// Option B: Providing only the name (Default battery to 100)
public Robot(String nombre) {
this.nombre = nombre;
this.nivelBateria = 100; // Default value
}
}Now the user of your class can choose:
Robot r1 = new Robot("R2-D2", 50); // Uses constructor A
Robot r2 = new Robot("C-3PO"); // Uses constructor BConstructor chaining (this(...))
In the previous example, we repeated code (this.nombre = nombre). In large classes, repeating initialization logic is bad practice.
We can call a constructor from another constructor using this(...).
Rule: This call must be the first line of the constructor.
// Main constructor (the one doing the work)
public Robot(String nombre, int nivelBateria) {
this.nombre = nombre;
this.nivelBateria = nivelBateria;
}
// Auxiliary constructor (delegates to the main one)
public Robot(String nombre) {
// "Calls the constructor above passing 'nombre' and a fixed 100"
this(nombre, 100);
}This is elegant and professional. You centralize the initialization logic in a single point.