Skip to main content
Create your own
Lesson illustration

Constructors: Building Objects

Hello! Welcome to your sixth lesson in our module on OOP Foundations.

In our last lesson, we completed our look at polymorphism by contrasting compile-time polymorphism (method overloading) with runtime polymorphism (method overriding). You learned that overloading is about providing multiple methods with the same name but different parameters within a single class, a concept resolved at compile time.

Today, we'll explore a crucial part of an object's life cycle, a topic where you'll see overloading used frequently. This lesson focuses on the learning outcome: to explain the role of constructors in object instantiation and initialization. Every time you use the new keyword in Java, you are using a constructor. Understanding how they work is absolutely fundamental to building any object-oriented system.

1. The Birth of an Object: Instantiation and Initialization

When you write new MyClass(), two things happen:

  1. Instantiation: The JVM allocates memory for a new object of type MyClass.
  2. Initialization: A special method called a constructor is executed to set the initial state of this new object.

Think of it this way: if a class is a blueprint for a house, the new keyword is the act of laying the foundation and reserving the land. The constructor is the construction crew that comes in to build the walls, install the plumbing, and paint the rooms, ensuring the house is in a valid, livable state from the moment it's finished.

Let's watch a video that walks through this process, from what happens without a constructor to how you can define your own.

Java Constructors - Full Tutorial

This video from 'Coding with John' provides a clear, step-by-step introduction to constructors. It explains what they are, how they are invoked, and why they are essential for proper object initialization.

Watch the video from the beginning up to 03:55. Pay close attention to: The definition of a constructor and its syntax (same name as the class, no return type). What the default constructor is and what it does (or doesn't do). How to create a custom constructor with parameters. The use of the this keyword to distinguish between instance variables and parameters.

2. The Different Flavors of Constructors

As you saw in the video, you have several options when it comes to defining constructors. Let's formalize these concepts. A comprehensive article from dev.to, "Java Constructors: A Complete Guide for Beginners & Pros," will serve as our main reference.

Java Constructors: A Complete Guide for Beginners & Pros

This article provides a thorough written guide to Java constructors. We'll start by reading about the main types.

Read the sections 'What is a Constructor in Java? A Formal Definition' and 'The Different Flavors: Types of Constructors'. This will solidify your understanding of the Default Constructor, No-Argument Constructor, and Parameterized Constructor.

Let's summarize the key types of constructors:

  • Default Constructor: If you write a class and don't define any constructor, the Java compiler provides one for you automatically. This constructor takes no arguments and initializes instance variables to their default values (e.g., 0 for numeric types, false for booleans, and null for object references).
Default Constructor in Java
This diagram illustrates how the Java compiler automatically adds a default, no-argument constructor to the compiled `.class` file if none is explicitly defined in the source code.
  • No-Argument (or No-Args) Constructor: This is a constructor that you write explicitly, but it takes no arguments. You would use this to provide custom default values or perform setup logic that doesn't require external data.

    public class User {
        private String role;
        // No-argument constructor
        public User() {
            this.role = "GUEST"; // Provide a sensible default
        }
    }
    
  • Parameterized Constructor: This is the most common and powerful type. It accepts parameters that are used to initialize the object's state, ensuring the object is created with specific, valid data from the start.

    public class User {
        private String username;
        private String email;
        // Parameterized constructor
        public User(String username, String email) {
            this.username = username;
            this.email = email;
        }
    }
    

Important Rule: The moment you define any constructor in your class (parameterized or not), the Java compiler will not provide the default constructor for you. If you still need a no-argument constructor, you must define it yourself.

3. Constructor Overloading and Chaining

Connecting back to our previous lesson, constructors can be overloaded just like regular methods. This is extremely useful as it provides multiple ways to create an object. For example, you might have one constructor for creating a User with just a username, and another for creating a User with a username and an email.

public class User {
    private String username;
    private String email;

    // 1. No-arg constructor
    public User() {
        this.username = "default_user";
        this.email = "default@example.com";
    }

    // 2. Overloaded constructor with one parameter
    public User(String username) {
        this.username = username;
        this.email = "default@example.com"; // Still provide a default for email
    }

    // 3. Overloaded constructor with two parameters
    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }
}

Did you notice the code duplication in the example above? The logic for setting default values is repeated. To adhere to the DRY (Don't Repeat Yourself) principle, we can use constructor chaining. One constructor can call another in the same class using this().

Let's improve our User class:

public class User {
    private String username;
    private String email;

    // The "main" constructor that does the work
    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

    // Overloaded constructor that chains to the main one
    public User(String username) {
        this(username, "default@example.com"); // Calls the (String, String) constructor
    }

    // No-arg constructor that also chains
    public User() {
        this("default_user", "default@example.com"); // Calls the (String, String) constructor
    }
}

Rule: When using this(), it must be the very first statement inside a constructor.

4. Special Use Cases: Private and Copy Constructors

While most constructors are public, there are important scenarios for other access levels.

Private Constructors

What if you want to prevent a class from being instantiated from the outside? You can make its constructor private. This is a key technique used in two common situations:

  1. Utility Classes: Classes that only contain static methods and constants (like java.lang.Math) should not be instantiated. A private constructor prevents this.
  2. Singleton Pattern: A design pattern that ensures a class has only one instance. The private constructor is used to control the creation process, which you'll learn about in a future module.

Let's see a quick example of a private constructor.

Java Constructors - Full Tutorial

The 'Coding with John' video concludes with a great explanation of why and how to use private constructors.

Watch from 05:59 to 07:16. Notice how making the constructor private in the Constants class results in a compile-time error when you try to use new from another class.

Copy Constructors

Although not a built-in feature in Java as it is in C++, you can implement a copy constructor. This is a constructor that takes an object of the same class as an argument and creates a new object by copying the data from the original. This is a common alternative to cloning.

Java Constructors: A Complete Guide for Beginners & Pros

Let's read about constructor chaining and copy constructors to complete our overview.

Read the sections 'Constructor Chaining: Teamwork Makes the Dream Work' and 'The Secret Player: The Copy Constructor'.


Test your understanding!

You are designing a Report class. A report must have a title and author. Optionally, it can have a version number, which defaults to 1 if not provided.

Your task is to implement the Report class with the following requirements:

  1. It should have instance variables for title (String), author (String), and version (int).
  2. Provide two public constructors:
    • One that accepts a title and an author.
    • Another that accepts a title, author, and version.
  3. Ensure there is no code duplication by using constructor chaining (this()).
// Your implementation here
class Report {
    private String title;
    private String author;
    private int version;

    // ...
}
Show answer
class Report {
    private String title;
    private String author;
    private int version;

    // This is the designated constructor that handles all initializations.
    public Report(String title, String author, int version) {
        if (title == null || title.trim().isEmpty()) {
            throw new IllegalArgumentException("Title cannot be empty.");
        }
        if (author == null || author.trim().isEmpty()) {
            throw new IllegalArgumentException("Author cannot be empty.");
        }
        this.title = title;
        this.author = author;
        this.version = version;
    }

    // This constructor chains to the main one, providing a default value for version.
    public Report(String title, String author) {
        this(title, author, 1); // Calls the other constructor
    }
    
    // Optional: a getter to verify the state
    @Override
    public String toString() {
        return "Report [title=" + title + ", author=" + author + ", version=" + version + "]";
    }
}

In this solution, the two-argument constructor delegates the responsibility of object creation to the three-argument constructor by calling this(title, author, 1). This centralizes the initialization logic and any validation, making the code cleaner and easier to maintain.

Conclusion

You've now explored the fundamental role constructors play in the lifecycle of every Java object. They are the gatekeepers that ensure every object is born in a valid and predictable state.

Key Takeaways:

  • Role: Constructors are special methods invoked by the new keyword to initialize a newly created object.
  • Rules: They have the same name as the class and no return type.
  • Types: You can use the compiler-provided default constructor, define your own no-argument constructor, or use a parameterized constructor to set initial state.
  • Overloading: A class can have multiple constructors with different parameter lists, providing flexible ways to create objects.
  • Chaining: Use this() to call one constructor from another within the same class to reduce code duplication.
  • Private Constructors: Used to control instantiation, typically for utility classes or implementing the Singleton pattern.

In our next lesson, we will dive deeper into one of the topics we touched on today. We will define the purpose and use of the static keyword for members and methods, which is essential for understanding concepts like utility classes and class-level variables.

Can't find a good explanation? Sign up and we'll make it for you

Sign up