Java Constructors
A constructor is a special block of code in Java that runs automatically when you create an object with new. Its job is to set up the object’s initial state, usually by assigning values to its fields, before any other code can use it. Every class has at least one constructor, even if you never write one yourself, because the compiler quietly supplies one for you. Understanding constructors deeply — how they’re chosen, chained, and executed — is essential to writing correct, bug-free Java classes.
Overview: What a Constructor Is and How It Works
A constructor looks like a method, but it is not one: it has no return type (not even void), and its name must exactly match the class name. When you write new Person("Alice", 30), three things happen in order: (1) the JVM allocates memory on the heap large enough to hold the object’s fields, (2) every field is set to its default value (0, 0.0, false, or null depending on type), and (3) the matching constructor runs, typically overwriting those defaults with real values. Only after the constructor finishes does new return a reference to the fully-initialized object.
If you don’t write any constructor at all, the compiler automatically inserts a no-argument default constructor that does nothing but call the superclass constructor (implicitly super(), which for most classes means Object‘s constructor). The moment you write even one constructor of your own, that automatic default constructor disappears — this is one of the most common sources of confusion for beginners, covered in Common Mistakes below.
Constructors can be overloaded, meaning a class can have multiple constructors with different parameter lists. Java picks the right one at compile time based on the arguments you pass to new. Constructors can also call each other using this(...), and a subclass constructor can call its parent’s constructor using super(...) — both must be the very first statement in the constructor body if used.
Constructor Syntax
class ClassName {
// fields
dataType fieldName;
// constructor
ClassName(parameterType parameterName, ...) {
// initialization code, e.g.
this.fieldName = parameterName;
}
}
| Part | Meaning |
|---|---|
| Name | Must be identical to the class name, including capitalization. |
| Return type | None at all — not even void. Adding one turns it into an ordinary method. |
| Access modifier | public, private, protected, or package-private, controlling who can call new on this constructor. |
| Parameters | Zero or more, used to overload constructors and supply initial values. |
this.field |
Refers to the object’s own field, distinguishing it from a same-named parameter. |
this(...) |
Calls another constructor in the same class; must be the first statement. |
super(...) |
Calls the parent class’s constructor; must be the first statement if used. |
Examples
Example 1: The Default Constructor and Field Defaults
public class Main {
static class Book {
String title;
double price;
Book() {
title = "Untitled";
price = 0.0;
System.out.println("Book created via default constructor");
}
}
public static void main(String[] args) {
Book b = new Book();
System.out.println(b.title + ", $" + b.price);
}
}
Output:
Book created via default constructor
Untitled, $0.0
Here we wrote our own no-argument constructor explicitly (this is different from the compiler-generated default constructor, though it behaves similarly). Before the constructor body runs, title and price already hold their type defaults (null and 0.0); the constructor body then overwrites them with the values we chose.
Example 2: Overloaded Constructors and this() Chaining
public class Main {
static class Rectangle {
double width;
double height;
Rectangle() {
this(1.0, 1.0);
System.out.println("No-arg constructor called");
}
Rectangle(double side) {
this(side, side);
System.out.println("Single-arg constructor called (square)");
}
Rectangle(double width, double height) {
this.width = width;
this.height = height;
System.out.println("Two-arg constructor called: " + width + "x" + height);
}
double area() {
return width * height;
}
}
public static void main(String[] args) {
Rectangle r1 = new Rectangle();
Rectangle r2 = new Rectangle(4.0);
Rectangle r3 = new Rectangle(3.0, 5.0);
System.out.println("r1 area: " + r1.area());
System.out.println("r2 area: " + r2.area());
System.out.println("r3 area: " + r3.area());
}
}
Output:
Two-arg constructor called: 1.0x1.0
No-arg constructor called
Two-arg constructor called: 4.0x4.0
Single-arg constructor called (square)
Two-arg constructor called: 3.0x5.0
r1 area: 1.0
r2 area: 16.0
r3 area: 15.0
Notice the order of the printed lines: when Rectangle() calls this(1.0, 1.0), the two-argument constructor runs completely first, and only after it returns does execution continue in Rectangle(). This is constructor chaining — it lets you funnel all the real initialization logic into one “master” constructor and have the others simply supply default values.
Example 3: super() Chaining and a Copy Constructor
public class Main {
static class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
Person(Person other) {
this.name = other.name;
this.age = other.age;
System.out.println("Copy constructor invoked");
}
}
static class Employee extends Person {
double salary;
Employee(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
@Override
public String toString() {
return name + " (" + age + "), $" + salary;
}
}
public static void main(String[] args) {
Employee e1 = new Employee("Alice", 30, 75000.0);
System.out.println(e1);
Person p2 = new Person(e1);
System.out.println(p2.name + " copied, age " + p2.age);
}
}
Output:
Alice (30), $75000.0
Copy constructor invoked
Alice copied, age 30
Employee‘s constructor calls super(name, age) so that Person‘s fields get initialized properly before Employee adds its own salary field. The second constructor in Person is a copy constructor — a common pattern (not a built-in language feature) that builds a new object by copying another object’s fields, which is useful when you want an independent duplicate rather than a second reference to the same object.
How Object Creation Works Step by Step (Under the Hood)
When the JVM executes new Employee("Alice", 30, 75000.0), it performs these steps in order:
- Allocate a block of memory on the heap sized to hold every field, including inherited ones from
PersonandObject. - Zero-initialize every field to its default (0, 0.0, false, or null).
- Run the constructor body’s first statement — if it is
super(...)orthis(...), that call happens now, recursively repeating this same process for the parent class (or sibling constructor) before continuing. - Execute the class’s instance field initializers and any instance initializer blocks, in the order they’re written in the source file.
- Execute the remaining statements in the constructor’s body.
- Return the reference to the now fully-initialized object to the caller.
This means the constructor chain always runs from the top of the inheritance hierarchy downward: Object‘s constructor runs first, then Person‘s, then Employee‘s — guaranteeing that by the time your subclass constructor body executes, every inherited field is already set up.
Common Mistakes
Mistake 1: Losing the default constructor without noticing
Once you add any constructor with parameters, Java stops generating the free no-argument constructor. Code that used to compile can suddenly break:
class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
class Test {
public static void main(String[] args) {
Point p = new Point(); // compile error: no Point() constructor
}
}
The fix is to explicitly add the no-argument constructor yourself if you still need it:
class Point {
int x, y;
Point() {
this(0, 0);
}
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Mistake 2: Accidentally giving a constructor a return type
If you add a return type — even void — Java no longer treats it as a constructor; it becomes a regular method that happens to share the class’s name and must be called explicitly:
class Robot {
String status;
void Robot() { // this is a METHOD, not a constructor!
status = "ready";
}
}
// new Robot() will leave status as null, because the real
// (invisible, compiler-generated) default constructor ran instead,
// and Robot() the method was never called.
Always double-check that a constructor has no return type at all — this typo is easy to miss during a quick read.
Best Practices
- Keep constructors focused on initialization — avoid heavy computation, I/O, or long-running work inside them.
- Use constructor chaining (
this(...)) to avoid duplicating initialization logic across overloaded constructors. - Initialize all
finalfields in every constructor path — the compiler will enforce this, but design your constructors so it’s natural rather than awkward. - Validate constructor arguments and throw an exception (like
IllegalArgumentException) for invalid input rather than silently accepting bad state. - Prefer a small number of well-designed constructors over many overloads; consider a builder pattern if a class has many optional parameters.
- Make a constructor
privatewhen you want to force object creation through a factory method or enforce a singleton. - Always call
super(...)explicitly when the parent class has no no-argument constructor, since Java otherwise inserts an implicitsuper()call that may not compile.
Practice Exercises
- Write a
Circleclass with aradiusfield, a constructor that takes the radius, and a no-argument constructor that chains to it with a default radius of 1.0. Add anarea()method and print the areas of two circles. - Write a
Vehicleclass with fieldsmakeandmodel, and a subclassCarthat adds anumDoorsfield. GiveCar‘s constructor a call tosuper(make, model)and print a full description of a createdCarobject. - Write a
Temperatureclass with a private constructor and a public static factory methodfromCelsius(double c)that returns a newTemperatureobject. Explain (in a comment) why you might want to prevent direct use ofnew Temperature(...)from outside the class.
Summary
- A constructor initializes a new object; it shares the class’s name and has no return type.
- If you write no constructor, Java supplies a no-argument default constructor automatically — but only until you add one of your own.
- Constructors can be overloaded, and one constructor can call another in the same class with
this(...), which must be the first statement. - A subclass constructor can call its parent’s constructor with
super(...), also as the first statement; this always runs before the subclass’s own initialization code. - Object creation order is: allocate memory, zero-initialize fields, run the constructor chain from the top of the hierarchy down, then return the reference.
- Common mistakes include losing the default constructor after adding a parameterized one, and accidentally giving a constructor a return type, turning it into a plain method.
